diff --git a/README.md b/README.md index af7ea5b..cdc38a4 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ CodeAstra is a modern, extensible, and lightweight code editor built using C++ and Qt6, designed to offer a fast, customizable, and feature-rich development experience. Inspired by NeoVim and VSCode, it **will** provide efficient file navigation, syntax highlighting, and a powerful plugin system, making it an ideal choice for developers who need speed, flexibility, and control. With a focus on performance and usability, the editor **will** support split views, an integrated terminal, customizable key bindings, and seamless Git integration, catering to both beginners and power users. +![CodeAstra Demo](assets/demo_ui.png) + > [!TIP] > > Join the [Matrix Server](https://matrix.to/#/#codeastra:matrix.org) for CodeAstra development log and discussion. diff --git a/assets/demo_ui.png b/assets/demo_ui.png new file mode 100644 index 0000000..08678e5 Binary files /dev/null and b/assets/demo_ui.png differ diff --git a/config/cxx.syntax.yaml b/config/cxx.syntax.yaml index 42d27f9..cf011f8 100644 --- a/config/cxx.syntax.yaml +++ b/config/cxx.syntax.yaml @@ -5,7 +5,7 @@ keywords: - regex: "\\b(char|class|const|double|enum|explicit|friend|inline|int|long|namespace|operator|private|protected|public|short|signals|signed|slots|static|struct|template|typedef|typename|union|unsigned|virtual|void|volatile|foreach)\\b" color: "#003478" # Dark Blue bold: true - - regex: "\\b(for|while|do|if|else)\\b" + - regex: "\\b(for|while|do|if|else|return)\\b" color: "#D9001D" # Bright Red - regex: "(? m_iconProvider; std::unique_ptr m_model; std::unique_ptr m_tree; + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; }; \ No newline at end of file diff --git a/resources.qrc b/resources.qrc index 3b5d61d..d6f8c6e 100644 --- a/resources.qrc +++ b/resources.qrc @@ -1,5 +1,6 @@ resources/app_icon.png + resources/themes/dark.qss \ No newline at end of file diff --git a/resources/themes/dark.qss b/resources/themes/dark.qss new file mode 100644 index 0000000..85c10b0 --- /dev/null +++ b/resources/themes/dark.qss @@ -0,0 +1,178 @@ +* { + font-family: 'Fira Code', 'Menlo', 'Monaco', "Consolas", "Courier New", "Monospace"; + font-size: 13pt; +} + +QMainWindow { + background-color: #1a1a1a; + color: #ffffff; + border-radius: 8px; +} + +QStatusBar { + background-color: #252525; + color: #e0e0e0; + border-top: 1px solid #383838; + font-size: 11pt; + padding: 6px 10px; +} + +QTreeView { + background-color: #1e1e1e; + color: #e0e0e0; + padding: 6px; + border-radius: 6px; +} + +QTreeView::item { + padding: 2px 4px; +} + +QTreeView::item:selected { + background: #2b5ea3; + color: white; + border-radius: 4px; +} + +QTreeView::item:hover { + background: #3c3c3c; +} + +QTreeView::item:focus { + outline: none; +} + +QScrollBar:vertical, QScrollBar:horizontal { + background: #1e1e1e; + width: 12px; + margin: 0px; +} + +QScrollBar::handle { + background: #666666; + border-radius: 6px; + margin: 2px; +} + +QScrollBar::handle:hover { + background: #888888; +} + +QMenuBar { + background-color: #252525; + color: white; + padding: 6px 10px; +} + +QMenu { + background-color: #2d2d2d; + color: white; + border: 1px solid #444444; + border-radius: 6px; + padding: 4px; +} + +QMenu::item { + padding: 6px 20px; + border-radius: 4px; +} + +QMenu::item:selected { + background-color: #3c3c3c; +} + +QMenu::item:hover { + background-color: #454545; +} + +QPlainTextEdit, QLineEdit { + background-color: #1e1e1e; + color: #e0e0e0; + border-radius: 6px; + padding: 8px; + border: 1px solid #333333; +} + +QPlainTextEdit { + selection-background-color: #2b5ea3; + selection-color: white; +} + +QPlainTextEdit:focus, QTreeView:focus { + border: 1px solid #3d75c2; +} + +QTabBar::tab { + background: #2d2d2d; + color: #d4d4d4; + padding: 8px 16px; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + margin-right: 2px; +} + +QTabBar::tab:selected { + background: #1e1e1e; + color: white; + border-top: 2px solid #3d75c2; +} + +QTabBar::tab:hover { + background: #353535; +} + +QToolTip { + background-color: #2d2d2d; + color: #e0e0e0; + border: 1px solid #444444; + padding: 6px; + border-radius: 6px; +} + +QPushButton { + background-color: #2b5ea3; + color: white; + border-radius: 6px; + padding: 8px 16px; + border: none; +} + +QPushButton:hover { + background-color: #3d75c2; +} + +QPushButton:pressed { + background-color: #214a80; +} + +QComboBox { + background-color: #2d2d2d; + color: #e0e0e0; + border-radius: 6px; + padding: 6px; + border: 1px solid #444444; +} + +QComboBox:hover { + border: 1px solid #3d75c2; +} + +QComboBox::drop-down { + border: none; +} + +QCheckBox { + color: #e0e0e0; + spacing: 8px; +} + +QCheckBox::indicator { + width: 18px; + height: 18px; + border-radius: 4px; + border: 1px solid #444444; +} + +QCheckBox::indicator:checked { + background-color: #2b5ea3; +} diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 63815f9..a6f4068 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -71,14 +71,16 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) } else if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Slash) { - addComment(); + addComment(); return; } else if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Backspace) { - moveCursor(QTextCursor::WordLeft, QTextCursor::KeepAnchor); - textCursor().removeSelectedText(); - textCursor().deletePreviousChar(); + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::WordLeft, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + setTextCursor(cursor); + return; } else @@ -263,7 +265,7 @@ void CodeEditor::highlightCurrentLine() QTextEdit::ExtraSelection selection; QColor lineColor = QColor(Qt::lightGray).lighter(60); - lineColor.setAlpha(100); + lineColor.setAlpha(80); selection.format.setBackground(lineColor); selection.format.setProperty(QTextFormat::FullWidthSelection, true); diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 411d5a7..8131b31 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -387,7 +387,11 @@ OperationResult FileManager::newFile(const QFileInfo &pathInfo, QString newFileP file.close(); } - FileManager::getInstance().setCurrentFileName(QString::fromStdString(filePath.string())); + QString fileName = QString::fromStdString(filePath.string()); + + FileManager::getInstance().setCurrentFileName(fileName); + FileManager::getInstance().loadFileInEditor(fileName); + return {true, filePath.filename().string() + " created successfully."}; } diff --git a/src/Tree.cpp b/src/Tree.cpp index 182b054..4cb21cf 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include Tree::Tree(QSplitter *splitter) : QObject(splitter), @@ -17,7 +20,7 @@ Tree::Tree(QSplitter *splitter) m_model(std::make_unique()), m_tree(std::make_unique(splitter)) { - connect(m_tree.get(), &QTreeView::doubleClicked, this, &Tree::openFile); + connect(m_tree.get(), &QTreeView::clicked, this, &Tree::openFile); } Tree::~Tree() {} @@ -33,18 +36,33 @@ void Tree::setupModel(const QString &directory) m_model->setRootPath(directory); m_model->setIconProvider(m_iconProvider.get()); m_model->setFilter(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot); + m_model->setReadOnly(false); } void Tree::setupTree() { + QFont baseFont = QApplication::font(); + QFont treeFont = baseFont; + treeFont.setPointSize(baseFont.pointSize() - 1); + + m_tree->setFont(treeFont); m_tree->setModel(m_model.get()); m_tree->setRootIndex(m_model->index(m_model->rootPath())); m_tree->setRootIsDecorated(true); m_tree->setAnimated(true); - m_tree->setIndentation(20); + m_tree->setIndentation(16); m_tree->setSortingEnabled(false); m_tree->sortByColumn(1, Qt::AscendingOrder); m_tree->setHeaderHidden(true); + m_tree->setUniformRowHeights(true); + + // Configure file/folder drag & drop + m_tree->setDragDropMode(QAbstractItemView::InternalMove); + m_tree->setDragEnabled(true); + m_tree->setAcceptDrops(true); + m_tree->setDropIndicatorShown(true); + m_tree->setDefaultDropAction(Qt::MoveAction); + m_tree->setEditTriggers(QAbstractItemView::NoEditTriggers); for (int i = 1; i <= m_model->columnCount(); ++i) { @@ -53,6 +71,37 @@ void Tree::setupTree() m_tree->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_tree.get(), &QTreeView::customContextMenuRequested, this, &Tree::showContextMenu); + + // Connect to dragEnter and dragMove events + m_tree->installEventFilter(this); +} + +bool Tree::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == m_tree.get()) + { + if (event->type() == QEvent::DragEnter) + { + QDragEnterEvent *dragEvent = static_cast(event); + if (dragEvent->mimeData()->hasUrls()) + { + dragEvent->acceptProposedAction(); + return true; + } + } + + else if (event->type() == QEvent::DragMove) + { + QDragMoveEvent *dragEvent = static_cast(event); + if (dragEvent->mimeData()->hasUrls()) + { + dragEvent->acceptProposedAction(); + return true; + } + } + } + + return QObject::eventFilter(obj, event); } void Tree::openFile(const QModelIndex &index) diff --git a/src/main.cpp b/src/main.cpp index 93247d6..4a01dc5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include // Function to create a round icon QIcon createRoundIcon(const QString &iconPath) @@ -31,29 +32,77 @@ QIcon createRoundIcon(const QString &iconPath) return QIcon(roundPixmap); } +QFont setFont() +{ + QStringList preferreFontFamilies = {"Monaco", "Menlo", "Consolas", "Courier New", "Monospace"}; + QStringList availableFamilies = QFontDatabase::families(); + + QString chosenFamily; + for(QString family: preferreFontFamilies) + { + if (availableFamilies.contains(family)) + { + chosenFamily = family; + break; + } + } + + if (chosenFamily.isEmpty()) + { + chosenFamily = QFontDatabase::systemFont(QFontDatabase::FixedFont).family(); + } + + QFont font; + font.setFamily(chosenFamily); + font.setFixedPitch(true); + font.setPointSize(13); + + return font; +} + +QPalette setPalette() +{ + QPalette palette; + palette.setColor(QPalette::Base, QColor("#1e1e1e")); + palette.setColor(QPalette::Text, QColor("#d4d4d4")); + + return palette; +} + +QString getStyleConfig() +{ + QFile styleFile(":/resources/themes/dark.qss"); + if (styleFile.open(QFile::ReadOnly)) + { + return QString::fromUtf8(styleFile.readAll()); + } + + return QString{}; +} + int main(int argc, char *argv[]) { QApplication app(argc, argv); - QIcon roundIcon = createRoundIcon(":/resources/app_icon.png"); - if (roundIcon.isNull()) + QIcon windowIcon = createRoundIcon(":/resources/app_icon.png"); + QFont font = setFont(); + QPalette palette = setPalette(); + QString theme = getStyleConfig(); + if (!theme.isEmpty()) { - qWarning() << "Failed to load round icon!"; + app.setStyleSheet(theme); } - QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); - font.setPointSize(12); - + app.setPalette(palette); app.setFont(font); - app.setWindowIcon(roundIcon); + app.setWindowIcon(windowIcon); + app.setStyle("Fusion"); - app.setApplicationVersion(QStringLiteral("0.1.0")); + app.setApplicationVersion(QStringLiteral("0.2.0")); app.setOrganizationName(QStringLiteral("Chris Dedman")); app.setApplicationName(QStringLiteral("CodeAstra")); app.setApplicationDisplayName(QStringLiteral("CodeAstra")); - app.setStyle("Fusion"); - QScopedPointer window(new MainWindow); window->show();