TUI #1

Merged
shaunrd0 merged 73 commits from ui into master 2026-01-25 20:57:37 +00:00
8 changed files with 80 additions and 64 deletions
Showing only changes of commit bdf942371c - Show all commits

View File

@ -6,7 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
cxx = "1.0.95" cxx = "1.0.95"
cxx-qt = "0.7" cxx-qt = "0.7"
cxx-qt-lib = { version="0.7", features = ["qt_full"] } cxx-qt-lib = { version="0.7", features = ["qt_full", "qt_gui"] }
log = { version = "0.4.27", features = [] } log = { version = "0.4.27", features = [] }
dirs = "6.0.0" dirs = "6.0.0"

View File

@ -5,7 +5,7 @@ CLIDE is an IDE written in Rust that supports both full and headless Linux envir
The following packages must be installed before the application will build. The following packages must be installed before the application will build.
```bash ```bash
sudo apt install qt6-base-dev qt6-declarative-dev qt6-tools-dev qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick qml6-module-qtquick-dialogs sudo apt install qt6-base-dev qt6-declarative-dev qt6-tools-dev qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick qml6-module-qtquick-dialogs qt6-svg-dev
``` ```
And of course, [Rust](https://www.rust-lang.org/tools/install). And of course, [Rust](https://www.rust-lang.org/tools/install).

View File

@ -8,6 +8,9 @@ fn main() {
// - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib. // - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib.
// - Qt Qml requires linking Qt Network on macOS // - Qt Qml requires linking Qt Network on macOS
.qt_module("Network") .qt_module("Network")
.qt_module("Gui")
.qt_module("Svg")
.qt_module("Xml")
.qml_module(QmlModule { .qml_module(QmlModule {
uri: "clide.module", uri: "clide.module",
rust_files: &["src/colors.rs", "src/filesystem.rs"], rust_files: &["src/colors.rs", "src/filesystem.rs"],

View File

@ -67,6 +67,7 @@ MenuBar {
} }
ClideMenuItem { ClideMenuItem {
action: actionOpen action: actionOpen
onTriggered: FileSystem.setDirectory(FileSystem.filePath)
} }
ClideMenuItem { ClideMenuItem {
action: actionSave action: actionSave

View File

@ -8,7 +8,7 @@ SplitView {
id: root id: root
// Path to the file selected in the tree view. // Path to the file selected in the tree view.
property string selectedFilePath; default property string selectedFilePath: FileSystem.filePath;
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
@ -38,23 +38,14 @@ SplitView {
StackLayout { StackLayout {
anchors.fill: parent anchors.fill: parent
// Shows the help text.
TextArea {
placeholderText: qsTr("File system view placeholder")
placeholderTextColor: "white"
readOnly: true
wrapMode: TextArea.Wrap
}
ClideTreeView { ClideTreeView {
id: clideTreeView id: clideTreeView
onFileClicked: path => root.currentFilePath = path onFileClicked: path => root.selectedFilePath = path
} }
} }
} }
ClideEditor { ClideEditor {
// Initialize using the Default trait in Rust QML singleton FileSystem. // Initialize using the Default trait in Rust QML singleton FileSystem.
filePath: FileSystem.filePath filePath: root.selectedFilePath
} }
} }

View File

@ -1,25 +1,32 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import clide.module 1.0 import clide.module 1.0
Rectangle { Rectangle {
id: root id: root
signal fileClicked(string filePath) signal fileClicked(string filePath)
color: RustColors.explorer_background
TreeView { TreeView {
id: fileSystemTreeView id: fileSystemTreeView
// rootIndex: FileSystem.rootIndex // rootIndex: FileSystem.rootIndex
property int lastIndex: -1 property int lastIndex: -1
// model: FileSystem model: FileSystem
anchors.fill: parent anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds
clip: true clip: true
Component.onCompleted: fileSystemTreeView.toggleExpanded(0) Component.onCompleted: {
FileSystem.setDirectory(FileSystem.filePath)
fileSystemTreeView.expandRecursively(0, 4)
}
// The delegate represents a single entry in the filesystem. // The delegate represents a single entry in the filesystem.
delegate: TreeViewDelegate { delegate: TreeViewDelegate {
@ -28,9 +35,6 @@ Rectangle {
implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250
implicitHeight: 25 implicitHeight: 25
// Since we have the 'ComponentBehavior Bound' pragma, we need to
// require these properties from our model. This is a convenient way
// to bind the properties provided by the model's role names.
required property int index required property int index
required property url filePath required property url filePath
required property string fileName required property string fileName
@ -40,11 +44,16 @@ Rectangle {
x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
source: treeDelegate.hasChildren ? (treeDelegate.expanded source: {
? "../icons/folder_open.svg" : "../icons/folder_closed.svg") // If the item has children, it's a directory.
: "../icons/generic_file.svg" if (treeDelegate.hasChildren) {
sourceSize.width: 20 return treeDelegate.expanded ?
sourceSize.height: 20 "../icons/folder-open-solid.svg" : "../icons/folder-solid.svg";
}
return "../icons/file-solid.svg"
}
sourceSize.width: 15
sourceSize.height: 15
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true smooth: true
@ -54,37 +63,23 @@ Rectangle {
contentItem: Text { contentItem: Text {
text: treeDelegate.fileName text: treeDelegate.fileName
color: RustColors.editor_text color: RustColors.explorer_text
} }
background: Rectangle { background: Rectangle {
// TODO: Fix flickering from color transition on states here.
color: (treeDelegate.index === fileSystemTreeView.lastIndex) color: (treeDelegate.index === fileSystemTreeView.lastIndex)
? RustColors.editor_highlight ? RustColors.explorer_text_selected
: (hoverHandler.hovered ? RustColors.active : "transparent") : (hoverHandler.hovered ? RustColors.explorer_hovered : "transparent")
} radius: 2.5
opacity: hoverHandler.hovered ? 0.75 : 1.0
// We color the directory icons with this MultiEffect, where we overlay Behavior on color {
// the colorization color ontop of the SVG icons. ColorAnimation {
// MultiEffect { duration: 300
// id: iconOverlay }
// }
// anchors.fill: directoryIcon }
// source: directoryIcon
// colorization: 1.0
// brightness: 1.0
// colorizationColor: {
// const isFile = treeDelegate.index === fileSystemTreeView.lastIndex
// && !treeDelegate.hasChildren;
// if (isFile)
// return Qt.lighter(RustColors.explorer_folder, 3)
//
// const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren;
// if (isExpandedFolder)
// return RustColors.explorer_forder_open
// else
// return RustColors.explorer_folder
// }
// }
HoverHandler { HoverHandler {
id: hoverHandler id: hoverHandler
@ -115,12 +110,15 @@ Rectangle {
Action { Action {
text: qsTr("Set as root index") text: qsTr("Set as root index")
onTriggered: { onTriggered: {
// fileSystemTreeView.rootIndex = fileSystemTreeView.index(treeDelegate.row, 0) console.log("Setting directory: " + treeDelegate.filePath)
FileSystem.setDirectory(treeDelegate.filePath)
} }
} }
Action { Action {
text: qsTr("Reset root index") text: qsTr("Reset root index")
// onTriggered: fileSystemTreeView.rootIndex = undefined onTriggered: {
FileSystem.setDirectory("")
}
} }
} }
} }

View File

@ -25,6 +25,9 @@ pub mod qobject {
#[qproperty(QColor, editor_highlighted_text)] #[qproperty(QColor, editor_highlighted_text)]
#[qproperty(QColor, editor_highlight)] #[qproperty(QColor, editor_highlight)]
#[qproperty(QColor, gutter)] #[qproperty(QColor, gutter)]
#[qproperty(QColor, explorer_hovered)]
#[qproperty(QColor, explorer_text)]
#[qproperty(QColor, explorer_text_selected)]
#[qproperty(QColor, explorer_background)] #[qproperty(QColor, explorer_background)]
#[qproperty(QColor, explorer_folder)] #[qproperty(QColor, explorer_folder)]
#[qproperty(QColor, explorer_folder_open)] #[qproperty(QColor, explorer_folder_open)]
@ -51,6 +54,9 @@ pub struct RustColorsImpl {
editor_highlighted_text: QColor, editor_highlighted_text: QColor,
editor_highlight: QColor, editor_highlight: QColor,
gutter: QColor, gutter: QColor,
explorer_hovered: QColor,
explorer_text: QColor,
explorer_text_selected: QColor,
explorer_background: QColor, explorer_background: QColor,
explorer_folder: QColor, explorer_folder: QColor,
explorer_folder_open: QColor, explorer_folder_open: QColor,
@ -75,7 +81,10 @@ impl Default for RustColorsImpl {
editor_highlighted_text: QColor::try_from("#ccced3").unwrap(), editor_highlighted_text: QColor::try_from("#ccced3").unwrap(),
editor_highlight: QColor::try_from("#ccced3").unwrap(), editor_highlight: QColor::try_from("#ccced3").unwrap(),
gutter: QColor::try_from("#1e1f22").unwrap(), gutter: QColor::try_from("#1e1f22").unwrap(),
explorer_background: QColor::try_from("#3c3f41").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_folder: QColor::try_from("#FFF").unwrap(), explorer_folder: QColor::try_from("#FFF").unwrap(),
explorer_folder_open: QColor::try_from("#FFF").unwrap(), explorer_folder_open: QColor::try_from("#FFF").unwrap(),
} }

View File

@ -6,29 +6,36 @@ pub mod qobject {
type QString = cxx_qt_lib::QString; type QString = cxx_qt_lib::QString;
include!("cxx-qt-lib/qmodelindex.h"); include!("cxx-qt-lib/qmodelindex.h");
type QModelIndex = cxx_qt_lib::QModelIndex; type QModelIndex = cxx_qt_lib::QModelIndex;
include!(<QtGui/QFileSystemModel>);
type QFileSystemModel;
} }
unsafe extern "RustQt" { unsafe extern "RustQt" {
// Export QML Types from Rust // Export QML Types from Rust
#[qobject] #[qobject]
#[base = QFileSystemModel]
#[qml_element] #[qml_element]
#[qml_singleton] #[qml_singleton]
#[qproperty(QString, file_path, cxx_name = "filePath")] #[qproperty(QString, file_path, cxx_name = "filePath")]
#[qproperty(QModelIndex, root_index, cxx_name = "rootIndex")] #[qproperty(QModelIndex, root_index, cxx_name = "rootIndex")]
type FileSystem = super::FileSystemImpl; type FileSystem = super::FileSystemImpl;
#[inherit]
#[cxx_name = "setRootPath"]
fn set_root_path(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex;
#[qinvokable] #[qinvokable]
#[cxx_override]
#[cxx_name = "columnCount"] #[cxx_name = "columnCount"]
pub fn column_count(self: &FileSystem, index: &QModelIndex) -> i32; fn column_count(self: &FileSystem, _index: &QModelIndex) -> i32;
#[qinvokable] #[qinvokable]
#[cxx_name = "readFile"] #[cxx_name = "readFile"]
fn read_file(self: &FileSystem, path: &QString) -> QString; fn read_file(self: &FileSystem, path: &QString) -> QString;
// TODO: Remove if unused in QML.
#[qinvokable] #[qinvokable]
#[cxx_name = "setInitialDirectory"] #[cxx_name = "setDirectory"]
fn set_initial_directory(self: &FileSystem, path: &QString); fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex;
} }
} }
@ -36,6 +43,7 @@ use cxx_qt_lib::{QModelIndex, QString};
use dirs; use dirs;
use std::fs; use std::fs;
// TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons.
pub struct FileSystemImpl { pub struct FileSystemImpl {
file_path: QString, file_path: QString,
root_index: QModelIndex, root_index: QModelIndex,
@ -64,21 +72,27 @@ impl qobject::FileSystem {
} }
// There will never be more than one column. // There will never be more than one column.
pub fn column_count(&self, _index: &QModelIndex) -> i32 { fn column_count(&self, _index: &QModelIndex) -> i32 {
1 1
} }
fn set_initial_directory(&self, path: &QString) { fn set_directory(self: std::pin::Pin<&mut Self>, path: &QString) -> QModelIndex {
if !path.is_empty() if !path.is_empty()
&& fs::metadata(path.to_string()) && fs::metadata(path.to_string())
.expect(format!("Failed to get metadata for file {}", path).as_str()) .expect(format!("Failed to get metadata for path {}", path).as_str())
.is_file() .is_dir()
{ {
// Open the file self.set_root_path(path)
// setRootPa
} else { } else {
// If the initial directory can't be opened, attempt to find the home directory. // If the initial directory can't be opened, attempt to find the home directory.
// dirs::home_dir() self.set_root_path(&QString::from(
dirs::home_dir()
.expect("Failed to get home directory")
.as_path()
.to_str()
.unwrap()
.to_string(),
))
} }
} }
} }