Clean up GUI.

This commit is contained in:
2026-02-08 14:26:26 -05:00
parent 344efc0042
commit 4bb4ce1882
11 changed files with 322 additions and 293 deletions

View File

@@ -5,7 +5,8 @@ fn main() {
"qml/main.qml",
"qml/ClideAboutWindow.qml",
"qml/ClideTreeView.qml",
"qml/ClideProjectView.qml",
"qml/ClideApplicationView.qml",
"qml/ClideExplorerView.qml",
"qml/ClideEditor.qml",
"qml/ClideEditorView.qml",
"qml/ClideMenuBar.qml",

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
//
// SPDX-License-Identifier: GNU General Public License v3.0 or later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import clide.module 1.0
import Logger 1.0
SplitView {
id: root
// Path to the directory of the project opened in clide.
required property string projectDir
anchors.fill: parent
// Customized handle to drag between the Navigation and the Editor.
handle: ClideHandle {
hovered: SplitHandle.hovered
pressed: SplitHandle.pressed
}
ClideExplorerView {
SplitView.fillHeight: true
SplitView.preferredWidth: 200
projectDir: root.projectDir
// Open files when clicked in the explorer.
onFileClicked: path => {
Logger.trace("Setting editor path from ClideExplorerView signal: " + path)
clideEditorView.filePath = path;
}
}
ClideEditorView {
id: clideEditorView
SplitView.fillHeight: true
SplitView.fillWidth: true
// Provide a path to the file currently open in the text editor.
// Initialized using the Default trait in Rust QML singleton FileSystem.
filePath: FileSystem.filePath
}
}

View File

@@ -9,7 +9,12 @@ import QtQuick.Layouts
import clide.module 1.0
import Logger 1.0
RowLayout {
Rectangle {
color: "transparent"
RowLayout {
anchors.fill: parent
// We use a flickable to synchronize the position of the editor and
// the line numbers. This is necessary because the line numbers can
// extend the available height.
@@ -57,12 +62,8 @@ RowLayout {
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.
// Draw an edge along the right side of the line number.
Rectangle {
id: indicator
@@ -133,4 +134,5 @@ RowLayout {
font: textArea.font
}
}
}
}

View File

@@ -9,22 +9,25 @@ import QtQuick.Layouts
import clide.module 1.0
import Logger 1.0
SplitView {
Rectangle {
id: root
// The path to the file to show in the text editor.
// This is updated by a signal caught within ClideProjectView.
// Initialized by the Default trait for the Rust QML singleton FileSystem.
// This is updated by a signal caught within ClideApplicationView.
required property string filePath
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
color: "transparent"
radius: 20
SplitView {
anchors.fill: parent
orientation: Qt.Vertical
// Customized handle to drag between the Editor and the Console.
handle: ClideHandle {
pressed: SplitHandle.pressed
hovered: SplitHandle.hovered
pressed: SplitHandle.pressed
}
Component.onCompleted: {
@@ -36,11 +39,10 @@ SplitView {
Logger.trace("Trace logs");
}
ClideEditor{
id: clideEditor
ClideEditor {
SplitView.preferredHeight: 650
}
ClideLogger {
id: areaConsole
}
}
}

62
qml/ClideExplorerView.qml Normal file
View File

@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
//
// SPDX-License-Identifier: GNU General Public License v3.0 or later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import clide.module 1.0
import Logger 1.0
Rectangle {
id: root
required property string projectDir
clip: true
color: RustColors.explorer_background
radius: 20
signal fileClicked(string path)
ColumnLayout {
anchors.fill: parent
spacing: 5
ClideBreadCrumbs {
id: breadCrumb
Layout.fillWidth: true
Layout.leftMargin: 15
Layout.rightMargin: 15
Layout.topMargin: 10
path: clideTreeView.rootDirectory
onCrumbClicked: path => {
Logger.trace("Crumb clicked: " + path);
clideTreeView.rootDirectory = path;
}
onResetRoot: {
clideTreeView.rootDirectory = clideTreeView.originalRootDirectory;
}
}
ClideTreeView {
id: clideTreeView
Layout.fillHeight: true
Layout.fillWidth: true
// Path to the directory opened in the file explorer.
originalRootDirectory: root.projectDir
rootDirectory: root.projectDir
// Pass the signal to the parent component using another signal.
onFileClicked: path => root.fileClicked(path)
onRootDirectoryChanged: {
Logger.log("Setting root directory: " + clideTreeView.rootDirectory);
breadCrumb.path = clideTreeView.rootDirectory;
}
}
}
}

View File

@@ -8,14 +8,12 @@ import QtQuick.Controls
import clide.module 1.0
import Logger 1.0
Item {
Rectangle {
color: "#111"
radius: 10
ListModel {
id: model
}
Rectangle {
anchors.fill: parent
color: "#111"
}
ListView {
id: listView
@@ -38,7 +36,6 @@ Item {
}
anchors.fill: parent
clip: true
model: model
delegate: Text {

View File

@@ -1,81 +0,0 @@
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
//
// SPDX-License-Identifier: GNU General Public License v3.0 or later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import clide.module 1.0
import Logger 1.0
SplitView {
id: root
// Path to the directory of the project opened in clide.
required property string projectDir
anchors.fill: parent
// Customized handle to drag between the Navigation and the Editor.
handle: ClideHandle {
id: verticalSplitHandle
hovered: SplitHandle.hovered
pressed: SplitHandle.pressed
}
Rectangle {
id: navigationView
SplitView.fillHeight: true
SplitView.maximumWidth: 250
SplitView.minimumWidth: 0
SplitView.preferredWidth: 200
color: RustColors.explorer_background
radius: 20
ColumnLayout {
spacing: 2
ClideBreadCrumbs {
id: breadCrumb
Layout.bottomMargin: 5
Layout.leftMargin: 15
Layout.topMargin: 5
path: clideTreeView.rootDirectory
onCrumbClicked: path => {
Logger.trace("Crumb clicked: " + path);
clideTreeView.rootDirectory = path;
}
}
ClideTreeView {
id: clideTreeView
height: navigationView.height
// Path to the directory opened in the file explorer.
originalRootDirectory: root.projectDir
rootDirectory: root.projectDir
width: navigationView.width
onFileClicked: path => clideEditor.filePath = path
onRootDirectoryChanged: {
Logger.log("Setting root directory: " + clideTreeView.rootDirectory);
breadCrumb.path = clideTreeView.rootDirectory;
}
}
}
}
ClideEditorView {
id: clideEditor
SplitView.fillWidth: true
// Provide a path to the file currently open in the text editor.
// Initialized using the Default trait in Rust QML singleton FileSystem.
filePath: FileSystem.filePath
}
}

View File

@@ -10,38 +10,30 @@ import clide.module 1.0
import Logger 1.0
TreeView {
id: fileSystemTreeView
id: root
property int lastIndex: -1
required property string originalRootDirectory
property string rootDirectory
property int rootIndent: 25
signal fileClicked(string filePath)
boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.StopAtBounds
clip: true
leftMargin: 25
// The model is implemented in filesystem.rs
model: FileSystem
rootIndex: FileSystem.setDirectory(fileSystemTreeView.rootDirectory)
// Set the root directory on the Rust model, returning the QModeIndex to use for the root of the tree view widget.
rootIndex: FileSystem.setDirectory(root.rootDirectory)
// Provide our own custom ScrollIndicator for the TreeView.
ScrollIndicator.vertical: ScrollIndicator {
active: true
implicitWidth: 15
contentItem: Rectangle {
color: RustColors.scrollbar
implicitHeight: 6
implicitWidth: 6
opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
Behavior on opacity {
OpacityAnimator {
duration: 500
}
}
ScrollBar.horizontal: ClideScrollBar {
sizeModifier: 3
}
ScrollBar.vertical: ClideScrollBar {
sizeModifier: 3
}
// The delegate represents a single entry in the filesystem.
@@ -53,38 +45,40 @@ TreeView {
required property int index
implicitHeight: 25
implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250
implicitWidth: root.width
indentation: 12
background: Rectangle {
color: current ? RustColors.explorer_folder_open : "transparent"
radius: 2.5
radius: 20
width: root.width - 15
}
// Item name.
contentItem: Text {
anchors.left: directoryIcon.right
anchors.left: itemIcon.right
anchors.leftMargin: 5
color: RustColors.explorer_text
text: treeDelegate.fileName
}
// Item Icon.
indicator: Label {
id: directoryIcon
id: itemIcon
anchors.verticalCenter: parent.verticalCenter
antialiasing: true
enabled: false
focus: false
font.family: localFont.font.family
font.pixelSize: 18
smooth: true
text: fileSystemTreeView.model.icon(filePath)
x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) + (indicator.visible ? indicator.width : 0)
// Get the icon from Rust implementation.
text: root.model.icon(filePath)
x: root.rootIndent + (treeDelegate.depth * treeDelegate.indentation) + (carrotIndicator.visible ? carrotIndicator.width : 0)
}
FontLoader {
id: localFont
source: "qrc:/fonts/saucecodepro-xlight.ttf"
}
// Directory carrot indicator.
Label {
id: indicator
id: carrotIndicator
anchors.verticalCenter: parent.verticalCenter
font.family: localFont.font.family
@@ -92,12 +86,11 @@ TreeView {
font.weight: localFont.font.weight
text: expanded ? "⮟" : "⮞"
visible: isTreeNode && hasChildren
x: padding + (depth * indentation)
x: (root.rootIndent - implicitWidth) + (depth * indentation)
}
// Apply colorization effects to the icon for the item.
MultiEffect {
id: iconOverlay
anchors.fill: directoryIcon
anchors.fill: itemIcon
brightness: 1.0
colorization: 1.0
colorizationColor: {
@@ -110,7 +103,7 @@ TreeView {
else
return RustColors.explorer_folder;
}
source: directoryIcon
source: itemIcon
}
HoverHandler {
id: hoverHandler
@@ -124,10 +117,10 @@ TreeView {
switch (button) {
case Qt.LeftButton:
if (treeDelegate.hasChildren) {
fileSystemTreeView.toggleExpanded(treeDelegate.row);
root.toggleExpanded(treeDelegate.row);
} else {
// If this model item doesn't have children, it means it's representing a file.
fileSystemTreeView.fileClicked(treeDelegate.filePath);
root.fileClicked(treeDelegate.filePath);
}
break;
case Qt.RightButton:
@@ -146,7 +139,7 @@ TreeView {
onTriggered: {
Logger.debug("Setting new root directory: " + treeDelegate.filePath);
fileSystemTreeView.rootDirectory = treeDelegate.filePath;
root.rootDirectory = treeDelegate.filePath;
}
}
}
@@ -155,8 +148,8 @@ TreeView {
text: qsTr("Reset root")
onTriggered: {
Logger.log("Resetting root directory: " + fileSystemTreeView.originalRootDirectory);
fileSystemTreeView.rootDirectory = fileSystemTreeView.originalRootDirectory;
Logger.log("Resetting root directory: " + root.originalRootDirectory);
root.rootDirectory = root.originalRootDirectory;
}
}
}
@@ -164,4 +157,10 @@ TreeView {
}
selectionModel: ItemSelectionModel {
}
FontLoader {
id: localFont
source: "qrc:/fonts/saucecodepro-xlight.ttf"
}
}

View File

@@ -5,7 +5,7 @@ import QtQuick.Layouts 1.15
import clide.module 1.0
import Logger 1.0
Item {
Rectangle {
id: root
property var fullPaths: []
@@ -13,36 +13,32 @@ Item {
property var segments: []
signal crumbClicked(string path)
signal resetRoot
function rebuildSegments() {
var cleaned = path;
function rebuildSegments(): string {
let cleaned = path;
if (cleaned.endsWith("/"))
cleaned = cleaned.slice(0, -1);
var parts = cleaned.split("/");
root.segments = [];
root.fullPaths = [];
var current = "";
Logger.trace("Building segments for path: " + cleaned);
for (var i = 0; i < parts.length; ++i) {
if (parts[i] === "") {
current = "/";
root.segments.push("/");
root.fullPaths.push("/");
} else {
if (current === "/")
current += parts[i];
else
segments = [];
fullPaths = [];
segments.push("/");
fullPaths.push("/");
let parts = cleaned.split("/");
let current = "";
// We already pushed the root `/` path above, so skip index 0.
for (let i = 1; i < parts.length; ++i) {
current += "/" + parts[i];
Logger.trace("Pushing path: " + parts[i] + " Current: " + current);
root.segments.push(parts[i]);
root.fullPaths.push(current);
segments.push(parts[i]);
fullPaths.push(current);
}
}
rep.model = root.segments;
// Update the model used in the Repeater to show the new segments.
repeater.model = segments;
}
anchors.leftMargin: 20
height: breadcrumbRow.implicitHeight
color: "transparent"
implicitHeight: breadcrumbRow.implicitHeight
width: parent.width
Component.onCompleted: rebuildSegments()
@@ -51,28 +47,32 @@ Item {
Flow {
id: breadcrumbRow
anchors.fill: parent
spacing: 2
width: parent.width
Repeater {
id: rep
id: repeater
model: root.segments
delegate: Text {
id: linkText
required property int index
required property string modelData
function getText() {
function getText(): string {
if (modelData === "/") {
return modelData;
}
Logger.trace("Getting valid text:" + modelData);
return modelData + "/";
}
// Show blue underlined hyperlink text if the mouse is hovering a segment.
color: mouseArea.containsMouse ? "#2a7fff" : RustColors.explorer_text
font.underline: mouseArea.containsMouse
text: getText()
// Click events for each path segment call signal so the parent can set the file explorer root path.
MouseArea {
id: mouseArea
@@ -80,8 +80,8 @@ Item {
hoverEnabled: true
onClicked: {
console.log("Breadcrumb clicked:", root.fullPaths[root.segments.indexOf(modelData)]);
root.crumbClicked(root.fullPaths[root.segments.indexOf(modelData)]);
Logger.info(index + "] Breadcrumb clicked:" + root.fullPaths[index]);
crumbClicked(root.fullPaths[index]);
}
}
}
@@ -90,7 +90,7 @@ Item {
TapHandler {
acceptedButtons: Qt.RightButton
onSingleTapped: (eventPoint, button) => contextMenu.popup()
onSingleTapped: contextMenu.popup()
}
ClideMenu {
id: contextMenu
@@ -100,8 +100,8 @@ Item {
text: qsTr("Reset root")
onTriggered: {
Logger.log("Resetting root directory: " + clideTreeView.originalRootDirectory);
clideTreeView.rootDirectory = clideTreeView.originalRootDirectory;
Logger.info("Resetting root directory from ClideBreadCrumbs");
resetRoot();
}
}
}

View File

@@ -11,9 +11,10 @@ ScrollBar {
id: scrollBar
// Height, opacitiy, width
property int h: scrollBar.interactive ? 4 * 2 : 4
property int h: scrollBar.interactive ? sizeModifier * 2 : sizeModifier
property int o: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
property int w: scrollBar.interactive ? 4 * 2 : 4
property int sizeModifier: 4
property int w: scrollBar.interactive ? sizeModifier * 2 : sizeModifier
// Scroll bar gutter
background: Rectangle {
@@ -25,6 +26,7 @@ ScrollBar {
// Fade the scrollbar gutter when inactive.
opacity: scrollBar.o
radius: 20
Behavior on opacity {
OpacityAnimator {
@@ -55,6 +57,7 @@ ScrollBar {
// Fade the scrollbar when inactive.
opacity: scrollBar.o
radius: 20
// Smooth transition between color changes based on the state above.
Behavior on color {

View File

@@ -22,9 +22,7 @@ ApplicationWindow {
menuBar: ClideMenuBar {
}
ClideProjectView {
id: clideProjectView
ClideApplicationView {
projectDir: appWindow.appContextPath
}
}