From aa8590cd5c9c634e03fc0db96328838a6bbf154b Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 31 Jan 2026 08:02:16 -0500 Subject: [PATCH 01/11] Add license headers. --- qml/ClideAboutWindow.qml | 4 +++- qml/ClideEditor.qml | 4 ++++ qml/ClideMenuBar.qml | 4 ++++ qml/ClideProjectView.qml | 4 ++++ qml/ClideTreeView.qml | 4 ++++ qml/main.qml | 4 ++++ src/gui.rs | 4 ++++ src/gui/colors.rs | 5 +++++ src/gui/filesystem.rs | 4 ++++ src/main.rs | 4 ++++ src/tui.rs | 4 ++++ src/tui/about.rs | 4 ++++ src/tui/app.rs | 4 ++++ src/tui/component.rs | 4 ++++ src/tui/editor.rs | 4 ++++ src/tui/editor_tab.rs | 4 ++++ src/tui/explorer.rs | 4 ++++ src/tui/logger.rs | 4 ++++ src/tui/menu_bar.rs | 4 ++++ 19 files changed, 76 insertions(+), 1 deletion(-) diff --git a/qml/ClideAboutWindow.qml b/qml/ClideAboutWindow.qml index 1b7c8a8..feacdc3 100644 --- a/qml/ClideAboutWindow.qml +++ b/qml/ClideAboutWindow.qml @@ -1,4 +1,6 @@ -// TODO: Header +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later import QtQuick import QtQuick.Controls.Basic diff --git a/qml/ClideEditor.qml b/qml/ClideEditor.qml index 512b470..0e74911 100644 --- a/qml/ClideEditor.qml +++ b/qml/ClideEditor.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/qml/ClideMenuBar.qml b/qml/ClideMenuBar.qml index 73a6655..3201f8d 100644 --- a/qml/ClideMenuBar.qml +++ b/qml/ClideMenuBar.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + import QtQuick import QtQuick.Controls diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index 05d55ef..1c56c53 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index 76ea446..49ca563 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/qml/main.qml b/qml/main.qml index fdbbf11..28271a1 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/src/gui.rs b/src/gui.rs index add9993..48b1e0b 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use crate::AppContext; use anyhow::Result; use cxx_qt_lib::{QMapPair, QMapPair_QString_QVariant, QString, QVariant}; diff --git a/src/gui/colors.rs b/src/gui/colors.rs index d113109..0d2b17f 100644 --- a/src/gui/colors.rs +++ b/src/gui/colors.rs @@ -1,4 +1,9 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + #[cxx_qt::bridge] + pub mod qobject { unsafe extern "C++" { include!("cxx-qt-lib/qcolor.h"); diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index 557203b..ffac3bf 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + #[cxx_qt::bridge] pub mod qobject { unsafe extern "C++" { diff --git a/src/main.rs b/src/main.rs index 917b90b..c975f90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use anyhow::{Context, Result, anyhow}; use clap::Parser; use log::{info, trace}; diff --git a/src/tui.rs b/src/tui.rs index d0569c5..2652ca7 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + mod about; mod app; mod component; diff --git a/src/tui/about.rs b/src/tui/about.rs index aa68961..7b53b7b 100644 --- a/src/tui/about.rs +++ b/src/tui/about.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::text::{Line, Span}; diff --git a/src/tui/app.rs b/src/tui/app.rs index d925d2c..d5569eb 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use crate::tui::about::About; use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger}; use crate::tui::component::{Action, Component, Focus, FocusState, Visibility, VisibleState}; diff --git a/src/tui/component.rs b/src/tui/component.rs index 2b73ae2..27b3999 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + #![allow(dead_code, unused_variables)] use crate::tui::component::Focus::Inactive; diff --git a/src/tui/editor.rs b/src/tui/editor.rs index a1f0081..0c20d3e 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use anyhow::{Context, Result, bail}; use edtui::{ diff --git a/src/tui/editor_tab.rs b/src/tui/editor_tab.rs index 76c2de4..21d8112 100644 --- a/src/tui/editor_tab.rs +++ b/src/tui/editor_tab.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use crate::tui::component::{Action, Component, Focus, FocusState}; use crate::tui::editor::Editor; use anyhow::{Context, Result, anyhow}; diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index fbd2063..cf295bb 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use anyhow::{Context, Result, bail}; use log::trace; diff --git a/src/tui/logger.rs b/src/tui/logger.rs index c68e441..d88cf68 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use log::{LevelFilter, trace}; use ratatui::buffer::Buffer; diff --git a/src/tui/menu_bar.rs b/src/tui/menu_bar.rs index 1de6b76..129d83a 100644 --- a/src/tui/menu_bar.rs +++ b/src/tui/menu_bar.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use crate::tui::component::{Action, Component, ComponentState, FocusState}; use crate::tui::menu_bar::MenuBarItemOption::{ About, CloseTab, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, -- 2.47.2 From 325cf285fc70783a74a5dc6547de4c42feaab0d4 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 31 Jan 2026 21:47:19 -0500 Subject: [PATCH 02/11] Try to use QSortFilterProxyModel via cxx-qt. This may be easier if I just wrap a QAbstractItemModel in C++ and use cxx-qt to pull it into QML? That way I could do C++ pointer things in C++ and rust things in rust. --- qml/ClideTreeView.qml | 11 ++-- src/gui/filesystem.rs | 134 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 124 insertions(+), 21 deletions(-) diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index 49ca563..47480c7 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -16,20 +16,23 @@ Rectangle { signal fileClicked(string filePath) + // https://doc.qt.io/qt-6/qml-qtquick-treeview.html TreeView { id: fileSystemTreeView anchors.margins: 15 property int lastIndex: -1 - model: FileSystem + model: FileSystemSortProxyModel { + id: fs + } anchors.fill: parent boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true Component.onCompleted: { - FileSystem.setDirectory(root.rootDirectory) + fs.setDirectory(root.rootDirectory) fileSystemTreeView.expandRecursively(0, -1) } @@ -122,13 +125,13 @@ Rectangle { text: qsTr("Set as root index") onTriggered: { console.log("Setting directory: " + treeDelegate.filePath) - FileSystem.setDirectory(treeDelegate.filePath) + FileSystemSortProxyModel.setDirectory(treeDelegate.filePath) } } Action { text: qsTr("Reset root index") onTriggered: { - FileSystem.setDirectory("") + FileSystemSortProxyModel.setDirectory("") } } } diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index ffac3bf..92cbb02 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -1,21 +1,48 @@ // SPDX-FileCopyrightText: 2026, Shaun Reed // // SPDX-License-Identifier: GNU General Public License v3.0 or later +use crate::gui::filesystem::qobject::{QAbstractItemModel}; +use cxx_qt_lib::{QModelIndex, QString}; +use dirs; +use log::warn; +use std::io::BufRead; +use std::{fs}; +use std::pin::Pin; +use syntect::easy::HighlightFile; +use syntect::highlighting::ThemeSet; +use syntect::html::{ + IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet, +}; +use syntect::parsing::SyntaxSet; #[cxx_qt::bridge] pub mod qobject { + // Import Qt Types from C++ unsafe extern "C++" { - // Import Qt Types from C++ include!("cxx-qt-lib/qstring.h"); type QString = cxx_qt_lib::QString; + include!("cxx-qt-lib/qmodelindex.h"); type QModelIndex = cxx_qt_lib::QModelIndex; + include!(); type QFileSystemModel; + + include!(); + type QSortFilterProxyModel; + + include!(); + type QAbstractItemModel; } + // Export QML classes from Rust unsafe extern "RustQt" { - // Export QML Types from Rust + #[qobject] + #[qml_element] + #[base = QSortFilterProxyModel] + #[qproperty(*mut FileSystem, inner)] + type FileSystemSortProxyModel = super::FileSystemSortProxyModelImpl; + #[qobject] #[base = QFileSystemModel] #[qml_element] @@ -23,7 +50,37 @@ pub mod qobject { #[qproperty(QString, file_path, cxx_name = "filePath")] #[qproperty(QModelIndex, root_index, cxx_name = "rootIndex")] type FileSystem = super::FileSystemImpl; + } + // Export QSortFilterProxyModel functions from Rust + // https://doc.qt.io/qt-6/qsortfilterproxymodel.html + unsafe extern "RustQt" { + #[inherit] + #[cxx_name = "setSourceModel"] + unsafe fn set_source_model( + self: Pin<&mut FileSystemSortProxyModel>, + source: *mut QAbstractItemModel, + ); + + #[cxx_override] + #[cxx_name = "filterAcceptsRow"] + const fn filter_accepts_row( + self: &FileSystemSortProxyModel, + source_row: i32, + source_parent: &QModelIndex, + ) -> bool; + + #[qinvokable] + #[cxx_name = "setDirectory"] + fn set_directory(self: Pin<&mut FileSystemSortProxyModel>, path: &QString) -> QModelIndex; + } + + // Custom initialization logic. + impl cxx_qt::Initialize for FileSystemSortProxyModel {} + + // Export QFileSystemModel functions from Rust + // https://doc.qt.io/qt-6/qfilesystemmodel.html + unsafe extern "RustQt" { #[inherit] #[cxx_name = "setRootPath"] fn set_root_path(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex; @@ -36,24 +93,67 @@ pub mod qobject { #[qinvokable] #[cxx_name = "readFile"] fn read_file(self: &FileSystem, path: &QString) -> QString; - - #[qinvokable] - #[cxx_name = "setDirectory"] - fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex; + // + // #[qinvokable] + // #[cxx_name = "setDirectory"] + // fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex; } } -use cxx_qt_lib::{QModelIndex, QString}; -use dirs; -use log::warn; -use std::fs; -use std::io::BufRead; -use syntect::easy::HighlightFile; -use syntect::highlighting::ThemeSet; -use syntect::html::{ - IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet, -}; -use syntect::parsing::SyntaxSet; +pub struct FileSystemSortProxyModelImpl { + inner: *mut qobject::FileSystem, +} + +impl Default for FileSystemSortProxyModelImpl { + fn default() -> Self { + let model = Self { + inner: std::ptr::null_mut(), + }; + model + } +} + +impl qobject::FileSystemSortProxyModel { + pub const fn filter_accepts_row(&self, _source_row: i32, _source_parent: &QModelIndex) -> bool { + false + } + + fn read_file(&self, path: &QString) -> QString { + if let Some(inner) = unsafe { self.inner().as_ref() } { + let pinned_inner = unsafe { Pin::new_unchecked(inner) }; + return pinned_inner.read_file(path) + } else { + panic!("Can't get inner()") + } + QString::default() + } + + // There will never be more than one column. + fn column_count(&self, _index: &QModelIndex) -> i32 { + 1 + } + + fn set_directory(self: Pin<&mut Self>, path: &QString) -> QModelIndex { + if let Some(inner) = unsafe { self.inner().as_mut() } { + let pinned_inner = unsafe { Pin::new_unchecked(inner) }; + return pinned_inner.set_directory(path) + } else { + panic!("Can't get inner()") + } + QModelIndex::default() + } +} + +impl cxx_qt::Initialize for qobject::FileSystemSortProxyModel { + fn initialize(self: core::pin::Pin<&mut Self>) { + let mut fs = FileSystemImpl::default(); + unsafe { + let model: *mut FileSystemImpl = std::ptr::from_mut(&mut fs); + let m: *mut QAbstractItemModel = model as *mut QAbstractItemModel; + self.set_source_model(m); + } + } +} // TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons. pub struct FileSystemImpl { -- 2.47.2 From c70bba16e4af064516180330145ee6a3583b05ab Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 1 Feb 2026 13:07:10 -0500 Subject: [PATCH 03/11] Revert "Try to use QSortFilterProxyModel via cxx-qt." This reverts commit 325cf285fc70783a74a5dc6547de4c42feaab0d4. --- qml/ClideTreeView.qml | 11 ++-- src/gui/filesystem.rs | 134 ++++++------------------------------------ 2 files changed, 21 insertions(+), 124 deletions(-) diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index 47480c7..49ca563 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -16,23 +16,20 @@ Rectangle { signal fileClicked(string filePath) - // https://doc.qt.io/qt-6/qml-qtquick-treeview.html TreeView { id: fileSystemTreeView anchors.margins: 15 property int lastIndex: -1 - model: FileSystemSortProxyModel { - id: fs - } + model: FileSystem anchors.fill: parent boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true Component.onCompleted: { - fs.setDirectory(root.rootDirectory) + FileSystem.setDirectory(root.rootDirectory) fileSystemTreeView.expandRecursively(0, -1) } @@ -125,13 +122,13 @@ Rectangle { text: qsTr("Set as root index") onTriggered: { console.log("Setting directory: " + treeDelegate.filePath) - FileSystemSortProxyModel.setDirectory(treeDelegate.filePath) + FileSystem.setDirectory(treeDelegate.filePath) } } Action { text: qsTr("Reset root index") onTriggered: { - FileSystemSortProxyModel.setDirectory("") + FileSystem.setDirectory("") } } } diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index 92cbb02..ffac3bf 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -1,48 +1,21 @@ // SPDX-FileCopyrightText: 2026, Shaun Reed // // SPDX-License-Identifier: GNU General Public License v3.0 or later -use crate::gui::filesystem::qobject::{QAbstractItemModel}; -use cxx_qt_lib::{QModelIndex, QString}; -use dirs; -use log::warn; -use std::io::BufRead; -use std::{fs}; -use std::pin::Pin; -use syntect::easy::HighlightFile; -use syntect::highlighting::ThemeSet; -use syntect::html::{ - IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet, -}; -use syntect::parsing::SyntaxSet; #[cxx_qt::bridge] pub mod qobject { - // Import Qt Types from C++ unsafe extern "C++" { + // Import Qt Types from C++ include!("cxx-qt-lib/qstring.h"); type QString = cxx_qt_lib::QString; - include!("cxx-qt-lib/qmodelindex.h"); type QModelIndex = cxx_qt_lib::QModelIndex; - include!(); type QFileSystemModel; - - include!(); - type QSortFilterProxyModel; - - include!(); - type QAbstractItemModel; } - // Export QML classes from Rust unsafe extern "RustQt" { - #[qobject] - #[qml_element] - #[base = QSortFilterProxyModel] - #[qproperty(*mut FileSystem, inner)] - type FileSystemSortProxyModel = super::FileSystemSortProxyModelImpl; - + // Export QML Types from Rust #[qobject] #[base = QFileSystemModel] #[qml_element] @@ -50,37 +23,7 @@ pub mod qobject { #[qproperty(QString, file_path, cxx_name = "filePath")] #[qproperty(QModelIndex, root_index, cxx_name = "rootIndex")] type FileSystem = super::FileSystemImpl; - } - // Export QSortFilterProxyModel functions from Rust - // https://doc.qt.io/qt-6/qsortfilterproxymodel.html - unsafe extern "RustQt" { - #[inherit] - #[cxx_name = "setSourceModel"] - unsafe fn set_source_model( - self: Pin<&mut FileSystemSortProxyModel>, - source: *mut QAbstractItemModel, - ); - - #[cxx_override] - #[cxx_name = "filterAcceptsRow"] - const fn filter_accepts_row( - self: &FileSystemSortProxyModel, - source_row: i32, - source_parent: &QModelIndex, - ) -> bool; - - #[qinvokable] - #[cxx_name = "setDirectory"] - fn set_directory(self: Pin<&mut FileSystemSortProxyModel>, path: &QString) -> QModelIndex; - } - - // Custom initialization logic. - impl cxx_qt::Initialize for FileSystemSortProxyModel {} - - // Export QFileSystemModel functions from Rust - // https://doc.qt.io/qt-6/qfilesystemmodel.html - unsafe extern "RustQt" { #[inherit] #[cxx_name = "setRootPath"] fn set_root_path(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex; @@ -93,67 +36,24 @@ pub mod qobject { #[qinvokable] #[cxx_name = "readFile"] fn read_file(self: &FileSystem, path: &QString) -> QString; - // - // #[qinvokable] - // #[cxx_name = "setDirectory"] - // fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex; + + #[qinvokable] + #[cxx_name = "setDirectory"] + fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex; } } -pub struct FileSystemSortProxyModelImpl { - inner: *mut qobject::FileSystem, -} - -impl Default for FileSystemSortProxyModelImpl { - fn default() -> Self { - let model = Self { - inner: std::ptr::null_mut(), - }; - model - } -} - -impl qobject::FileSystemSortProxyModel { - pub const fn filter_accepts_row(&self, _source_row: i32, _source_parent: &QModelIndex) -> bool { - false - } - - fn read_file(&self, path: &QString) -> QString { - if let Some(inner) = unsafe { self.inner().as_ref() } { - let pinned_inner = unsafe { Pin::new_unchecked(inner) }; - return pinned_inner.read_file(path) - } else { - panic!("Can't get inner()") - } - QString::default() - } - - // There will never be more than one column. - fn column_count(&self, _index: &QModelIndex) -> i32 { - 1 - } - - fn set_directory(self: Pin<&mut Self>, path: &QString) -> QModelIndex { - if let Some(inner) = unsafe { self.inner().as_mut() } { - let pinned_inner = unsafe { Pin::new_unchecked(inner) }; - return pinned_inner.set_directory(path) - } else { - panic!("Can't get inner()") - } - QModelIndex::default() - } -} - -impl cxx_qt::Initialize for qobject::FileSystemSortProxyModel { - fn initialize(self: core::pin::Pin<&mut Self>) { - let mut fs = FileSystemImpl::default(); - unsafe { - let model: *mut FileSystemImpl = std::ptr::from_mut(&mut fs); - let m: *mut QAbstractItemModel = model as *mut QAbstractItemModel; - self.set_source_model(m); - } - } -} +use cxx_qt_lib::{QModelIndex, QString}; +use dirs; +use log::warn; +use std::fs; +use std::io::BufRead; +use syntect::easy::HighlightFile; +use syntect::highlighting::ThemeSet; +use syntect::html::{ + IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet, +}; +use syntect::parsing::SyntaxSet; // TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons. pub struct FileSystemImpl { -- 2.47.2 From 048d40eb83e548aceac0c86b95c8daf2ee4238a1 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 1 Feb 2026 13:07:27 -0500 Subject: [PATCH 04/11] Fix TreeView root index. Parent folders will now be hidden based on the root folder selection in the GUI. --- README.md | 18 ++++++++++++++++++ qml/ClideTreeView.qml | 5 +++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8a2fff5..f80aab6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,24 @@ And of course, [Rust](https://www.rust-lang.org/tools/install). curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` +## Dependencies + +This project requires at least Qt 6.7. To check your Qt version + +```bash +qmake6 -query QT_VERSION +``` + +Use the [Qt Installer](https://www.qt.io/development/download) to download and install the Qt version of your choice. + +**You must set the QMAKE variable before building clide**. This should be a path to `qmake6` binary installed on your system. +The following export is the default installation path for Qt 6.7 on Ubuntu 24.04 + +```bash +export QMAKE=$HOME/Qt/6.7.3/gcc_64/bin/qmake6 +export LD_LIBRARY_PATH=$HOME/Qt/6.7.3/gcc_64/lib +``` + ## Usage To install and run clide diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index 49ca563..ea40c88 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -27,6 +27,7 @@ Rectangle { boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true + rootIndex: FileSystem.rootIndex Component.onCompleted: { FileSystem.setDirectory(root.rootDirectory) @@ -122,13 +123,13 @@ Rectangle { text: qsTr("Set as root index") onTriggered: { console.log("Setting directory: " + treeDelegate.filePath) - FileSystem.setDirectory(treeDelegate.filePath) + FileSystem.rootIndex = FileSystem.setDirectory(treeDelegate.filePath) } } Action { text: qsTr("Reset root index") onTriggered: { - FileSystem.setDirectory("") + FileSystem.rootIndex = FileSystem.setDirectory(root.rootDirectory) } } } -- 2.47.2 From db2f878018569f72472b7d49725ed5d42627f08c Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 1 Feb 2026 13:44:43 -0500 Subject: [PATCH 05/11] Fix about page image loading. --- README.md | 3 +-- build.rs | 1 + {icons => images}/kilroy-256.png | Bin qml/ClideAboutWindow.qml | 2 +- qml/ClideTreeView.qml | 1 - resources.qrc | 5 +++++ 6 files changed, 8 insertions(+), 4 deletions(-) rename {icons => images}/kilroy-256.png (100%) create mode 100644 resources.qrc diff --git a/README.md b/README.md index f80aab6..133b943 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ And of course, [Rust](https://www.rust-lang.org/tools/install). curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` -## Dependencies - This project requires at least Qt 6.7. To check your Qt version ```bash @@ -144,6 +142,7 @@ Some helpful links for reading up on QML if you're just getting started. * [All QML Controls Types](https://doc.qt.io/qt-6/qtquick-controls-qmlmodule.html) * [KDAB CXX-Qt Book](https://kdab.github.io/cxx-qt/book/) * [github.com/KDAB/cxx-qt](https://github.com/KDAB/cxx-qt) +* [QML and C++ Intergration](https://doc.qt.io/qt-6/qtqml-cppintegration-overview.html) ### Plugins diff --git a/build.rs b/build.rs index 1d20876..29ac905 100644 --- a/build.rs +++ b/build.rs @@ -18,6 +18,7 @@ fn main() { .qt_module("Gui") .qt_module("Svg") .qt_module("Xml") + .qrc("./resources.qrc") .files(["src/gui/colors.rs", "src/gui/filesystem.rs"]) .build(); } diff --git a/icons/kilroy-256.png b/images/kilroy-256.png similarity index 100% rename from icons/kilroy-256.png rename to images/kilroy-256.png diff --git a/qml/ClideAboutWindow.qml b/qml/ClideAboutWindow.qml index feacdc3..6d02ec0 100644 --- a/qml/ClideAboutWindow.qml +++ b/qml/ClideAboutWindow.qml @@ -31,7 +31,7 @@ ApplicationWindow { anchors.top: parent.top anchors.margins: 20 - source: "../icons/kilroy-256.png" + source: "qrc:/images/kilroy.png" sourceSize.width: 80 sourceSize.height: 80 fillMode: Image.PreserveAspectFit diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index ea40c88..17d7e24 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -4,7 +4,6 @@ import QtQuick import QtQuick.Controls -import QtQuick.Layouts import clide.module 1.0 diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..abfa83c --- /dev/null +++ b/resources.qrc @@ -0,0 +1,5 @@ + + + images/kilroy-256.png + + \ No newline at end of file -- 2.47.2 From 0fac2b71ab777a3e4d4cd9627eae4cd59a66b147 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 1 Feb 2026 15:35:40 -0500 Subject: [PATCH 06/11] Fix TreeView nesting for ColumnLayout. --- qml/ClideProjectView.qml | 19 ++- qml/ClideTreeView.qml | 265 +++++++++++++++++++++------------------ src/gui/colors.rs | 6 +- 3 files changed, 161 insertions(+), 129 deletions(-) diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index 1c56c53..3cd08b4 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -43,11 +43,26 @@ SplitView { SplitView.preferredWidth: 200 SplitView.maximumWidth: 250 - StackLayout { - anchors.fill: parent + ColumnLayout { + spacing: 2 + Rectangle { + width: navigationView.width + height: breadCrumb.height + 5 + + color: RustColors.explorer_text + Text { + id: breadCrumb + text: clideTreeView.rootDirectory + elide: Text.ElideLeft + horizontalAlignment: Text.AlignHCenter + } + } + ClideTreeView { id: clideTreeView onFileClicked: path => root.projectDir = path + width: navigationView.width + height: navigationView.height // Path to the directory opened in the file explorer. rootDirectory: root.projectDir diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index 17d7e24..9a58e0b 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -3,153 +3,170 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later import QtQuick +import QtQuick.Effects import QtQuick.Controls import clide.module 1.0 -Rectangle { - id: root - color: RustColors.explorer_background +TreeView { + id: fileSystemTreeView + model: FileSystem + + property int lastIndex: -1 required property string rootDirectory signal fileClicked(string filePath) - TreeView { - id: fileSystemTreeView - anchors.margins: 15 - property int lastIndex: -1 + rootIndex: FileSystem.rootIndex + leftMargin: 5 + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + clip: true - model: FileSystem - anchors.fill: parent - boundsBehavior: Flickable.StopAtBounds - boundsMovement: Flickable.StopAtBounds - clip: true - rootIndex: FileSystem.rootIndex + Component.onCompleted: { + FileSystem.rootIndex = FileSystem.setDirectory(fileSystemTreeView.rootDirectory) + } - Component.onCompleted: { - FileSystem.setDirectory(root.rootDirectory) - fileSystemTreeView.expandRecursively(0, -1) + // The delegate represents a single entry in the filesystem. + delegate: TreeViewDelegate { + id: treeDelegate + indentation: 6 + implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 + implicitHeight: 25 + + required property int index + required property url filePath + required property string fileName + + indicator: Image { + id: directoryIcon + + function setSourceImage() { + let folderOpen = "data:image/svg+xml;utf8,"; + let folderClosed = "data:image/svg+xml;utf8,"; + let file = "data:image/svg+xml;utf8,"; + // If the item has children, it's a directory. + if (treeDelegate.hasChildren) { + return treeDelegate.expanded ? + folderOpen : folderClosed; + } else { + return file + } + } + + x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) + anchors.verticalCenter: parent.verticalCenter + source: setSourceImage() + sourceSize.width: 15 + sourceSize.height: 15 + fillMode: Image.PreserveAspectFit + + smooth: true + antialiasing: true + asynchronous: true } - // The delegate represents a single entry in the filesystem. - delegate: TreeViewDelegate { - id: treeDelegate - indentation: 8 - implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 - implicitHeight: 25 + contentItem: Text { + text: treeDelegate.fileName + color: RustColors.explorer_text + } - required property int index - required property url filePath - required property string fileName + background: Rectangle { + // TODO: Fix flickering from color transition on states here. + color: (treeDelegate.index === fileSystemTreeView.lastIndex) + ? RustColors.explorer_text_selected + : (hoverHandler.hovered ? RustColors.explorer_hovered : "transparent") + radius: 2.5 + opacity: hoverHandler.hovered ? 0.75 : 1.0 - indicator: Image { - id: directoryIcon - - function setSourceImage() { - let folderOpen = "data:image/svg+xml;utf8,"; - let folderClosed = "data:image/svg+xml;utf8,"; - let file = "data:image/svg+xml;utf8,"; - // If the item has children, it's a directory. - if (treeDelegate.hasChildren) { - return treeDelegate.expanded ? - folderOpen : folderClosed; - } else { - return file - } - } - - x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) - anchors.verticalCenter: parent.verticalCenter - source: setSourceImage() - sourceSize.width: 15 - sourceSize.height: 15 - fillMode: Image.PreserveAspectFit - - smooth: true - antialiasing: true - asynchronous: true - } - - contentItem: Text { - text: treeDelegate.fileName - color: RustColors.explorer_text - } - - background: Rectangle { - // TODO: Fix flickering from color transition on states here. - color: (treeDelegate.index === fileSystemTreeView.lastIndex) - ? RustColors.explorer_text_selected - : (hoverHandler.hovered ? RustColors.explorer_hovered : "transparent") - radius: 2.5 - opacity: hoverHandler.hovered ? 0.75 : 1.0 - - Behavior on color { - ColorAnimation { - duration: 300 - } - } - } - - HoverHandler { - id: hoverHandler - } - - TapHandler { - acceptedButtons: Qt.LeftButton | Qt.RightButton - onSingleTapped: (eventPoint, button) => { - switch (button) { - case Qt.LeftButton: - fileSystemTreeView.toggleExpanded(treeDelegate.row) - fileSystemTreeView.lastIndex = treeDelegate.index - // If this model item doesn't have children, it means it's - // representing a file. - if (!treeDelegate.hasChildren) - root.fileClicked(treeDelegate.filePath) - break; - case Qt.RightButton: - if (treeDelegate.hasChildren) - contextMenu.popup(); - break; - } - } - } - - Menu { - id: contextMenu - Action { - text: qsTr("Set as root index") - onTriggered: { - console.log("Setting directory: " + treeDelegate.filePath) - FileSystem.rootIndex = FileSystem.setDirectory(treeDelegate.filePath) - } - } - Action { - text: qsTr("Reset root index") - onTriggered: { - FileSystem.rootIndex = FileSystem.setDirectory(root.rootDirectory) - } + Behavior on color { + ColorAnimation { + duration: 300 } } } - // Provide our own custom ScrollIndicator for the TreeView. - ScrollIndicator.vertical: ScrollIndicator { - active: true - implicitWidth: 15 + MultiEffect { + id: iconOverlay - contentItem: Rectangle { - implicitWidth: 6 - implicitHeight: 6 + anchors.fill: directoryIcon + source: directoryIcon + colorization: 1.0 + brightness: 1.0 + colorizationColor: { + const isFile = !treeDelegate.hasChildren; + if (isFile) + return Qt.lighter(RustColors.explorer_folder, 2) - color: RustColors.scrollbar - opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0 + const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren; + if (isExpandedFolder) + return Qt.darker(RustColors.explorer_folder, 2) + else + return RustColors.explorer_folder + } + } - Behavior on opacity { - OpacityAnimator { - duration: 500 - } + HoverHandler { + id: hoverHandler + acceptedDevices: PointerDevice.Mouse + } + + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onSingleTapped: (eventPoint, button) => { + switch (button) { + case Qt.LeftButton: + fileSystemTreeView.toggleExpanded(treeDelegate.row) + fileSystemTreeView.lastIndex = treeDelegate.index + // If this model item doesn't have children, it means it's + // representing a file. + if (!treeDelegate.hasChildren) + root.fileClicked(treeDelegate.filePath) + break; + case Qt.RightButton: + if (treeDelegate.hasChildren) + contextMenu.popup(); + break; + } + } + } + + Menu { + id: contextMenu + Action { + text: qsTr("Set as root index") + onTriggered: { + console.log("Setting directory: " + treeDelegate.filePath) + FileSystem.rootIndex = FileSystem.setDirectory(treeDelegate.filePath) + } + } + Action { + text: qsTr("Reset root index") + onTriggered: { + console.log("Reset root index") + FileSystem.rootIndex = FileSystem.setDirectory(fileSystemTreeView.rootDirectory) + } + } + } + } + + // Provide our own custom ScrollIndicator for the TreeView. + ScrollIndicator.vertical: ScrollIndicator { + active: true + implicitWidth: 15 + + contentItem: Rectangle { + implicitWidth: 6 + implicitHeight: 6 + + color: RustColors.scrollbar + opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 500 } } } diff --git a/src/gui/colors.rs b/src/gui/colors.rs index 0d2b17f..fea8ea1 100644 --- a/src/gui/colors.rs +++ b/src/gui/colors.rs @@ -88,10 +88,10 @@ impl Default for RustColorsImpl { gutter: QColor::try_from("#1e1f22").unwrap(), explorer_hovered: QColor::try_from("#4c5053").unwrap(), explorer_text: QColor::try_from("#3b3b3b").unwrap(), - explorer_text_selected: QColor::try_from("#8b8b8b").unwrap(), - explorer_background: QColor::try_from("#676c70").unwrap(), + explorer_text_selected: QColor::try_from("#262626").unwrap(), + explorer_background: QColor::try_from("#1E1F22").unwrap(), explorer_folder: QColor::try_from("#54585b").unwrap(), - explorer_folder_open: QColor::try_from("#FFF").unwrap(), + explorer_folder_open: QColor::try_from("#2b2b2b").unwrap(), } } } -- 2.47.2 From a5bed9ed2c7cf6214dbdc1fd0ae2b5cdfc4c2ca4 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 1 Feb 2026 17:15:21 -0500 Subject: [PATCH 07/11] Fix panic when loading bad text in the GUI. --- qml/ClideEditor.qml | 5 +-- qml/ClideProjectView.qml | 12 ++++--- qml/ClideTreeView.qml | 3 +- src/gui/colors.rs | 5 ++- src/gui/filesystem.rs | 70 ++++++++++++++++++++++------------------ 5 files changed, 54 insertions(+), 41 deletions(-) diff --git a/qml/ClideEditor.qml b/qml/ClideEditor.qml index 0e74911..464637c 100644 --- a/qml/ClideEditor.qml +++ b/qml/ClideEditor.qml @@ -153,12 +153,13 @@ SplitView { id: areaConsole height: 100 - placeholderText: qsTr("Placeholder for bash terminal.") + width: parent.width + placeholderText: qsTr("shaun@pc:~/Code/clide$ ") placeholderTextColor: "white" readOnly: true wrapMode: TextArea.Wrap background: Rectangle { - color: RustColors.editor_background + color: RustColors.terminal_background implicitHeight: 100 // border.color: control.enabled ? RustColors.active : RustColors.inactive } diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index 3cd08b4..a5a5c0c 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -45,22 +45,25 @@ SplitView { ColumnLayout { spacing: 2 + // TODO: Make a ClideBreadCrumb element to support select parent paths as root Rectangle { width: navigationView.width - height: breadCrumb.height + 5 - - color: RustColors.explorer_text + height: 25 + color: RustColors.explorer_background Text { id: breadCrumb + anchors.fill: parent text: clideTreeView.rootDirectory + color: RustColors.explorer_text elide: Text.ElideLeft horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter } } ClideTreeView { id: clideTreeView - onFileClicked: path => root.projectDir = path + onFileClicked: path => clideEditor.filePath = path width: navigationView.width height: navigationView.height @@ -70,6 +73,7 @@ SplitView { } } ClideEditor { + id: clideEditor SplitView.fillWidth: true // Provide a path to the file currently open in the text editor. diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index 9a58e0b..ad0cd4d 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -119,11 +119,10 @@ TreeView { switch (button) { case Qt.LeftButton: fileSystemTreeView.toggleExpanded(treeDelegate.row) - fileSystemTreeView.lastIndex = treeDelegate.index // If this model item doesn't have children, it means it's // representing a file. if (!treeDelegate.hasChildren) - root.fileClicked(treeDelegate.filePath) + fileSystemTreeView.fileClicked(treeDelegate.filePath) break; case Qt.RightButton: if (treeDelegate.hasChildren) diff --git a/src/gui/colors.rs b/src/gui/colors.rs index fea8ea1..33ed86b 100644 --- a/src/gui/colors.rs +++ b/src/gui/colors.rs @@ -36,6 +36,7 @@ pub mod qobject { #[qproperty(QColor, explorer_background)] #[qproperty(QColor, explorer_folder)] #[qproperty(QColor, explorer_folder_open)] + #[qproperty(QColor, terminal_background)] type RustColors = super::RustColorsImpl; } } @@ -65,6 +66,7 @@ pub struct RustColorsImpl { explorer_background: QColor, explorer_folder: QColor, explorer_folder_open: QColor, + terminal_background: QColor, } impl Default for RustColorsImpl { @@ -87,11 +89,12 @@ impl Default for RustColorsImpl { editor_highlight: QColor::try_from("#ccced3").unwrap(), gutter: QColor::try_from("#1e1f22").unwrap(), explorer_hovered: QColor::try_from("#4c5053").unwrap(), - explorer_text: QColor::try_from("#3b3b3b").unwrap(), + explorer_text: QColor::try_from("#FFF").unwrap(), explorer_text_selected: QColor::try_from("#262626").unwrap(), explorer_background: QColor::try_from("#1E1F22").unwrap(), explorer_folder: QColor::try_from("#54585b").unwrap(), explorer_folder_open: QColor::try_from("#2b2b2b").unwrap(), + terminal_background: QColor::try_from("#2C2E32").unwrap(), } } } diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index ffac3bf..09d7aa5 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -48,14 +48,16 @@ use dirs; use log::warn; use std::fs; use std::io::BufRead; -use syntect::easy::HighlightFile; +use std::path::Path; +use syntect::easy::{HighlightFile, HighlightLines}; use syntect::highlighting::ThemeSet; use syntect::html::{ IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet, }; use syntect::parsing::SyntaxSet; +use syntect::util::LinesWithEndings; -// TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons. +// TODO: Implement a provider for QFileSystemModel::setIconProvider for icons. pub struct FileSystemImpl { file_path: QString, root_index: QModelIndex, @@ -76,42 +78,46 @@ impl qobject::FileSystem { if path.is_empty() { return QString::default(); } - if !fs::metadata(path.to_string()) - .expect(format!("Failed to get file metadata {path:?}").as_str()) - .is_file() - { + let meta = fs::metadata(path.to_string()) + .expect(format!("Failed to get file metadata {path:?}").as_str()); + if !meta.is_file() { warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file"); return QString::default(); } - let ss = SyntaxSet::load_defaults_nonewlines(); - let ts = ThemeSet::load_defaults(); - let theme = &ts.themes["base16-ocean.dark"]; + let path_str = path.to_string(); + if let Ok(lines) = fs::read_to_string(path_str.as_str()) { + let ss = SyntaxSet::load_defaults_nonewlines(); + let ts = ThemeSet::load_defaults(); + let theme = &ts.themes["base16-ocean.dark"]; + let lang = ss + .find_syntax_by_extension( + Path::new(path_str.as_str()) + .extension() + .map(|s| s.to_str()) + .unwrap_or_else(|| Some("md")) + .expect("Failed to get file extension"), + ) + .unwrap_or_else(|| ss.find_syntax_plain_text()); + let mut highlighter = HighlightLines::new(lang, theme); + let (mut output, _bg) = start_highlighted_html_snippet(theme); + for line in LinesWithEndings::from(lines.as_str()) { + let regions = highlighter + .highlight_line(line, &ss) + .expect("Failed to highlight"); - let mut highlighter = - HighlightFile::new(path.to_string(), &ss, theme).expect("Failed to create highlighter"); - let (mut output, _bg) = start_highlighted_html_snippet(theme); - let mut line = String::new(); - while highlighter - .reader - .read_line(&mut line) - .expect("Failed to read file.") - > 0 - { - let regions = highlighter - .highlight_lines - .highlight_line(&line, &ss) - .expect("Failed to highlight"); + append_highlighted_html_for_styled_line( + ®ions[..], + IncludeBackground::Yes, + &mut output, + ) + .expect("Failed to insert highlighted html"); + } - append_highlighted_html_for_styled_line( - ®ions[..], - IncludeBackground::Yes, - &mut output, - ) - .expect("Failed to insert highlighted html"); - line.clear(); + output.push_str("\n"); + QString::from(output) + } else { + return QString::default(); } - output.push_str("\n"); - QString::from(output) } // There will never be more than one column. -- 2.47.2 From 4cc43916cbb55030a4df9482311b1180109d6f0e Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 1 Feb 2026 18:23:44 -0500 Subject: [PATCH 08/11] Update breadcrumbs when root directory changes. --- qml/ClideProjectView.qml | 5 +++++ qml/ClideTreeView.qml | 24 +++++++++------------ src/gui/filesystem.rs | 45 ++++++++++++++++++---------------------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index a5a5c0c..4a324e5 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -68,7 +68,12 @@ SplitView { height: navigationView.height // Path to the directory opened in the file explorer. + originalRootDirectory: root.projectDir rootDirectory: root.projectDir + onRootDirectoryChanged: { + console.log(clideTreeView.rootDirectory) + breadCrumb.text = clideTreeView.rootDirectory + } } } } diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index ad0cd4d..1eb99dc 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -14,25 +14,21 @@ TreeView { property int lastIndex: -1 - required property string rootDirectory + required property string originalRootDirectory + property string rootDirectory signal fileClicked(string filePath) - - rootIndex: FileSystem.rootIndex + rootIndex: FileSystem.setDirectory(fileSystemTreeView.rootDirectory) leftMargin: 5 boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true - Component.onCompleted: { - FileSystem.rootIndex = FileSystem.setDirectory(fileSystemTreeView.rootDirectory) - } - // The delegate represents a single entry in the filesystem. delegate: TreeViewDelegate { id: treeDelegate - indentation: 6 + indentation: 8 implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 implicitHeight: 25 @@ -125,8 +121,7 @@ TreeView { fileSystemTreeView.fileClicked(treeDelegate.filePath) break; case Qt.RightButton: - if (treeDelegate.hasChildren) - contextMenu.popup(); + contextMenu.popup(); break; } } @@ -136,16 +131,17 @@ TreeView { id: contextMenu Action { text: qsTr("Set as root index") + enabled: treeDelegate.hasChildren onTriggered: { - console.log("Setting directory: " + treeDelegate.filePath) - FileSystem.rootIndex = FileSystem.setDirectory(treeDelegate.filePath) + console.log("Setting new root directory: " + treeDelegate.filePath) + fileSystemTreeView.rootDirectory = treeDelegate.filePath } } Action { text: qsTr("Reset root index") onTriggered: { - console.log("Reset root index") - FileSystem.rootIndex = FileSystem.setDirectory(fileSystemTreeView.rootDirectory) + console.log("Resetting root directory: " + fileSystemTreeView.originalRootDirectory) + fileSystemTreeView.rootDirectory = fileSystemTreeView.originalRootDirectory } } } diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index 09d7aa5..9d897b7 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -2,6 +2,19 @@ // // SPDX-License-Identifier: GNU General Public License v3.0 or later +use cxx_qt_lib::{QModelIndex, QString}; +use dirs; +use log::warn; +use std::fs; +use std::path::Path; +use syntect::easy::HighlightLines; +use syntect::highlighting::ThemeSet; +use syntect::html::{ + IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet, +}; +use syntect::parsing::SyntaxSet; +use syntect::util::LinesWithEndings; + #[cxx_qt::bridge] pub mod qobject { unsafe extern "C++" { @@ -21,7 +34,6 @@ pub mod qobject { #[qml_element] #[qml_singleton] #[qproperty(QString, file_path, cxx_name = "filePath")] - #[qproperty(QModelIndex, root_index, cxx_name = "rootIndex")] type FileSystem = super::FileSystemImpl; #[inherit] @@ -43,24 +55,9 @@ pub mod qobject { } } -use cxx_qt_lib::{QModelIndex, QString}; -use dirs; -use log::warn; -use std::fs; -use std::io::BufRead; -use std::path::Path; -use syntect::easy::{HighlightFile, HighlightLines}; -use syntect::highlighting::ThemeSet; -use syntect::html::{ - IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet, -}; -use syntect::parsing::SyntaxSet; -use syntect::util::LinesWithEndings; - // TODO: Implement a provider for QFileSystemModel::setIconProvider for icons. pub struct FileSystemImpl { file_path: QString, - root_index: QModelIndex, } // Default is explicit to make the editor open this source file initially. @@ -68,7 +65,6 @@ impl Default for FileSystemImpl { fn default() -> Self { Self { file_path: QString::from(file!()), - root_index: Default::default(), } } } @@ -134,14 +130,13 @@ impl qobject::FileSystem { self.set_root_path(path) } else { // If the initial directory can't be opened, attempt to find the home directory. - self.set_root_path(&QString::from( - dirs::home_dir() - .expect("Failed to get home directory") - .as_path() - .to_str() - .unwrap() - .to_string(), - )) + let homedir = dirs::home_dir() + .expect("Failed to get home directory") + .as_path() + .to_str() + .unwrap() + .to_string(); + self.set_root_path(&QString::from(homedir)) } } } -- 2.47.2 From b9eee50e52f98d0c94e04f3712ca36cb721c8af6 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 1 Feb 2026 18:40:00 -0500 Subject: [PATCH 09/11] Fix menu bar colors. --- qml/ClideMenuBar.qml | 19 +++++++++---------- qml/main.qml | 8 +------- src/gui/colors.rs | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/qml/ClideMenuBar.qml b/qml/ClideMenuBar.qml index 3201f8d..0675623 100644 --- a/qml/ClideMenuBar.qml +++ b/qml/ClideMenuBar.qml @@ -8,10 +8,16 @@ import QtQuick.Controls import clide.module 1.0 MenuBar { + // Background for this MenuBar. + background: Rectangle { + color: RustColors.menubar + border.color: RustColors.explorer_background + } + // Base settings for each Menu. component ClideMenu : Menu { background: Rectangle { - color: RustColors.menubar + color: RustColors.explorer_background implicitWidth: 100 radius: 2 } @@ -23,7 +29,7 @@ MenuBar { background: Rectangle { color: root.hovered ? RustColors.hovered : RustColors.unhovered - radius: 2.5 + radius: 1.0 } contentItem: IconLabel { color: "black" @@ -32,13 +38,6 @@ MenuBar { } } - // Background for this MenuBar. - background: Rectangle { - color: RustColors.menubar - border.color: RustColors.menubar_border - } - - // // File Menu Action { @@ -79,7 +78,7 @@ MenuBar { MenuSeparator { background: Rectangle { border.color: color - color: RustColors.menubar_border + color: RustColors.explorer_background implicitHeight: 3 implicitWidth: 200 } diff --git a/qml/main.qml b/qml/main.qml index 28271a1..85200b3 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -19,13 +19,7 @@ ApplicationWindow { required property string appContextPath - menuBar: ClideMenuBar { - } - - Rectangle { - anchors.fill: parent - color: RustColors.gutter - } + menuBar: ClideMenuBar { } MessageDialog { id: errorDialog diff --git a/src/gui/colors.rs b/src/gui/colors.rs index 33ed86b..a413442 100644 --- a/src/gui/colors.rs +++ b/src/gui/colors.rs @@ -75,7 +75,7 @@ impl Default for RustColorsImpl { hovered: QColor::try_from("#303234").unwrap(), unhovered: QColor::try_from("#3c3f41").unwrap(), pressed: QColor::try_from("#4b4f51").unwrap(), - menubar: QColor::try_from("#3c3f41").unwrap(), + menubar: QColor::try_from("#262626").unwrap(), menubar_border: QColor::try_from("#575757").unwrap(), scrollbar: QColor::try_from("#4b4f51").unwrap(), scrollbar_active: QColor::try_from("#4b4f51").unwrap(), -- 2.47.2 From be383869b20f17541e6b2d8a3066904c1d0c508d Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 1 Feb 2026 18:52:11 -0500 Subject: [PATCH 10/11] Add context menu on breadcrumbs. The only option is to reset the root directory. --- qml/ClideProjectView.qml | 18 ++++++++++++++++++ qml/ClideTreeView.qml | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index 4a324e5..34b3ff1 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -59,6 +59,24 @@ SplitView { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } + + TapHandler { + acceptedButtons: Qt.RightButton + onSingleTapped: (eventPoint, button) => { + contextMenu.popup() + } + } + + Menu { + id: contextMenu + Action { + text: qsTr("Reset root index") + onTriggered: { + console.log("Resetting root directory: " + clideTreeView.originalRootDirectory) + clideTreeView.rootDirectory = clideTreeView.originalRootDirectory + } + } + } } ClideTreeView { diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index 1eb99dc..cf68a74 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -28,7 +28,7 @@ TreeView { // The delegate represents a single entry in the filesystem. delegate: TreeViewDelegate { id: treeDelegate - indentation: 8 + indentation: 12 implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 implicitHeight: 25 -- 2.47.2 From e5b91eaed823988fdd64b24ac0df6a24543e3c14 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 1 Feb 2026 20:20:39 -0500 Subject: [PATCH 11/11] Add basic debug logger. --- build.rs | 2 ++ qml/ClideEditor.qml | 25 +++++++++-------- qml/ClideLogger.qml | 58 ++++++++++++++++++++++++++++++++++++++++ qml/ClideProjectView.qml | 9 +++---- qml/ClideTreeView.qml | 5 ++-- qml/Logger/Logger.qml | 30 +++++++++++++++++++++ qml/Logger/qmldir | 1 + qml/main.qml | 1 + src/gui/colors.rs | 16 +++++++++-- 9 files changed, 125 insertions(+), 22 deletions(-) create mode 100644 qml/ClideLogger.qml create mode 100644 qml/Logger/Logger.qml create mode 100644 qml/Logger/qmldir diff --git a/build.rs b/build.rs index 29ac905..664dd6e 100644 --- a/build.rs +++ b/build.rs @@ -8,6 +8,8 @@ fn main() { "qml/ClideProjectView.qml", "qml/ClideEditor.qml", "qml/ClideMenuBar.qml", + "qml/ClideLogger.qml", + "qml/Logger/Logger.qml", ])) // Link Qt's Network library // - Qt Core is always linked diff --git a/qml/ClideEditor.qml b/qml/ClideEditor.qml index 464637c..8e3558e 100644 --- a/qml/ClideEditor.qml +++ b/qml/ClideEditor.qml @@ -7,6 +7,7 @@ import QtQuick.Controls import QtQuick.Layouts import clide.module 1.0 +import Logger 1.0 SplitView { id: root @@ -78,6 +79,9 @@ SplitView { text: parent.index + 1 verticalAlignment: Text.AlignVCenter width: parent.width - indicator.width + background: Rectangle { + color: RustColors.terminal_background + } } // Draw edge along the right side of the line number. Rectangle { @@ -149,20 +153,8 @@ SplitView { } } } - TextArea { + ClideLogger { id: areaConsole - - height: 100 - width: parent.width - placeholderText: qsTr("shaun@pc:~/Code/clide$ ") - placeholderTextColor: "white" - readOnly: true - wrapMode: TextArea.Wrap - background: Rectangle { - color: RustColors.terminal_background - implicitHeight: 100 - // border.color: control.enabled ? RustColors.active : RustColors.inactive - } } // We use an inline component to customize the horizontal and vertical @@ -208,4 +200,11 @@ SplitView { } } } + + + Component.onCompleted: { + // Show logging is working. + Logger.debug("Debug console ready") + Logger.warn("Warnings show up too") + } } diff --git a/qml/ClideLogger.qml b/qml/ClideLogger.qml new file mode 100644 index 0000000..c7b185b --- /dev/null +++ b/qml/ClideLogger.qml @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + +import QtQuick +import QtQuick.Controls + +import clide.module 1.0 +import Logger 1.0 + +Item { + ListModel { id: model } + + Rectangle { + anchors.fill: parent + color: "#111" + } + + ListView { + id: listView + anchors.fill: parent + model: model + clip: true + + function getLogColor(level) { + switch (level) { + case "INFO": + return RustColors.info_log + break; + case "DEBUG": + return RustColors.debug_log + break; + case "WARN": + return RustColors.warn_log + break; + case "ERROR": + return RustColors.error_log + break; + default: + return RustColors.info_log + break; + } + } + + delegate: Text { + text: `[${level}] ${message}` + font.family: "monospace" + color: listView.getLogColor(level) + } + } + + Connections { + target: Logger + function onLogged(level, message) { + model.append({ level, message }) + } + } +} diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index 34b3ff1..7ef39f6 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -7,6 +7,7 @@ import QtQuick.Controls import QtQuick.Layouts import clide.module 1.0 +import Logger 1.0 SplitView { id: root @@ -62,9 +63,7 @@ SplitView { TapHandler { acceptedButtons: Qt.RightButton - onSingleTapped: (eventPoint, button) => { - contextMenu.popup() - } + onSingleTapped: (eventPoint, button) => contextMenu.popup() } Menu { @@ -72,7 +71,7 @@ SplitView { Action { text: qsTr("Reset root index") onTriggered: { - console.log("Resetting root directory: " + clideTreeView.originalRootDirectory) + Logger.log("Resetting root directory: " + clideTreeView.originalRootDirectory) clideTreeView.rootDirectory = clideTreeView.originalRootDirectory } } @@ -89,7 +88,7 @@ SplitView { originalRootDirectory: root.projectDir rootDirectory: root.projectDir onRootDirectoryChanged: { - console.log(clideTreeView.rootDirectory) + Logger.log(clideTreeView.rootDirectory) breadCrumb.text = clideTreeView.rootDirectory } } diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index cf68a74..1ba5db5 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -7,6 +7,7 @@ import QtQuick.Effects import QtQuick.Controls import clide.module 1.0 +import Logger 1.0 TreeView { id: fileSystemTreeView @@ -133,14 +134,14 @@ TreeView { text: qsTr("Set as root index") enabled: treeDelegate.hasChildren onTriggered: { - console.log("Setting new root directory: " + treeDelegate.filePath) + Logger.debug("Setting new root directory: " + treeDelegate.filePath) fileSystemTreeView.rootDirectory = treeDelegate.filePath } } Action { text: qsTr("Reset root index") onTriggered: { - console.log("Resetting root directory: " + fileSystemTreeView.originalRootDirectory) + Logger.log("Resetting root directory: " + fileSystemTreeView.originalRootDirectory) fileSystemTreeView.rootDirectory = fileSystemTreeView.originalRootDirectory } } diff --git a/qml/Logger/Logger.qml b/qml/Logger/Logger.qml new file mode 100644 index 0000000..5f80b54 --- /dev/null +++ b/qml/Logger/Logger.qml @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + +pragma Singleton +import QtQuick + +QtObject { + signal logged(string level, string message) + + function log(msg) { + console.log(msg) + logged("INFO", msg) + } + + function debug(msg) { + console.log(msg) + logged("DEBUG", msg) + } + + function warn(msg) { + console.warn(msg) + logged("WARN", msg) + } + + function error(msg) { + console.error(msg) + logged("ERROR", msg) + } +} diff --git a/qml/Logger/qmldir b/qml/Logger/qmldir new file mode 100644 index 0000000..019474e --- /dev/null +++ b/qml/Logger/qmldir @@ -0,0 +1 @@ +singleton Logger 1.0 Logger.qml diff --git a/qml/main.qml b/qml/main.qml index 85200b3..6f71d32 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -27,6 +27,7 @@ ApplicationWindow { title: qsTr("Error") } ClideProjectView { + id: clideProjectView projectDir: appWindow.appContextPath } } diff --git a/src/gui/colors.rs b/src/gui/colors.rs index a413442..c7c1fbd 100644 --- a/src/gui/colors.rs +++ b/src/gui/colors.rs @@ -37,6 +37,10 @@ pub mod qobject { #[qproperty(QColor, explorer_folder)] #[qproperty(QColor, explorer_folder_open)] #[qproperty(QColor, terminal_background)] + #[qproperty(QColor, info_log)] + #[qproperty(QColor, debug_log)] + #[qproperty(QColor, warn_log)] + #[qproperty(QColor, error_log)] type RustColors = super::RustColorsImpl; } } @@ -67,6 +71,10 @@ pub struct RustColorsImpl { explorer_folder: QColor, explorer_folder_open: QColor, terminal_background: QColor, + info_log: QColor, + debug_log: QColor, + warn_log: QColor, + error_log: QColor, } impl Default for RustColorsImpl { @@ -83,7 +91,7 @@ impl Default for RustColorsImpl { linenumber: QColor::try_from("#94989b").unwrap(), active: QColor::try_from("#a9acb0").unwrap(), inactive: QColor::try_from("#FFF").unwrap(), - editor_background: QColor::try_from("#2b2b2b").unwrap(), + editor_background: QColor::try_from("#111111").unwrap(), editor_text: QColor::try_from("#acaea3").unwrap(), editor_highlighted_text: QColor::try_from("#ccced3").unwrap(), editor_highlight: QColor::try_from("#ccced3").unwrap(), @@ -94,7 +102,11 @@ impl Default for RustColorsImpl { explorer_background: QColor::try_from("#1E1F22").unwrap(), explorer_folder: QColor::try_from("#54585b").unwrap(), explorer_folder_open: QColor::try_from("#2b2b2b").unwrap(), - terminal_background: QColor::try_from("#2C2E32").unwrap(), + terminal_background: QColor::try_from("#111111").unwrap(), + info_log: QColor::try_from("#C4FFFF").unwrap(), + debug_log: QColor::try_from("#55ff99").unwrap(), + warn_log: QColor::try_from("#ffaa00").unwrap(), + error_log: QColor::try_from("#ff5555").unwrap(), } } } -- 2.47.2