Format with qmlformat.

This commit is contained in:
2026-02-02 18:08:37 -05:00
parent 5af09485a3
commit 29024e3999
8 changed files with 280 additions and 273 deletions

View File

@@ -9,11 +9,12 @@ import clide.module 1.0
ApplicationWindow { ApplicationWindow {
id: root id: root
width: 450
height: 350 color: RustColors.gutter
// Create the window with no frame and keep it on top. // Create the window with no frame and keep it on top.
flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
color: RustColors.gutter height: 350
width: 450
// Hide the window when it loses focus. // Hide the window when it loses focus.
onActiveChanged: { onActiveChanged: {
@@ -27,48 +28,38 @@ ApplicationWindow {
id: logo id: logo
anchors.left: parent.left anchors.left: parent.left
anchors.margins: 20
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.margins: 20
source: "qrc:/images/kilroy.png"
sourceSize.width: 80
sourceSize.height: 80
fillMode: Image.PreserveAspectFit
smooth: true
antialiasing: true antialiasing: true
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit
smooth: true
source: "qrc:/images/kilroy.png"
sourceSize.height: 80
sourceSize.width: 80
} }
ScrollView { ScrollView {
anchors.top: logo.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 20 anchors.margins: 20
anchors.right: parent.right
anchors.top: logo.bottom
TextArea { TextArea {
selectedTextColor: RustColors.editor_highlighted_text
selectionColor: RustColors.editor_highlight
horizontalAlignment: Text.AlignHCenter
textFormat: Text.RichText
text: qsTr("<h3>About CLIDE</h3>"
+ "<p>A simple text editor written in Rust and QML using CXX-Qt.</p>"
+ "<p>Personal website <a href=\"http://shaunreed.com\">shaunreed.com</a></p>"
+ "<p>Project notes <a href=\"http://knoats.com\">knoats.com</a></p>"
+ "<p>This project is developed at <a href=\"http://git.shaunreed.com/shaunrd0/clide\">git.shaunreed.com</a></p>"
+ "<p><a href=\"https://github.com/KDAB/cxx-qt\">KDAB CXX-Qt repository</a></p>"
+ "<p>Copyright (C) 2026 Shaun Reed, all rights reserved.</p>")
color: RustColors.editor_text
wrapMode: Text.WordWrap
readOnly: true
antialiasing: true antialiasing: true
background: null background: null
color: RustColors.editor_text
horizontalAlignment: Text.AlignHCenter
readOnly: true
selectedTextColor: RustColors.editor_highlighted_text
selectionColor: RustColors.editor_highlight
text: qsTr("<h3>About CLIDE</h3>" + "<p>A simple text editor written in Rust and QML using CXX-Qt.</p>" + "<p>Personal website <a href=\"http://shaunreed.com\">shaunreed.com</a></p>" + "<p>Project notes <a href=\"http://knoats.com\">knoats.com</a></p>" + "<p>This project is developed at <a href=\"http://git.shaunreed.com/shaunrd0/clide\">git.shaunreed.com</a></p>" + "<p><a href=\"https://github.com/KDAB/cxx-qt\">KDAB CXX-Qt repository</a></p>" + "<p>Copyright (C) 2026 Shaun Reed, all rights reserved.</p>")
textFormat: Text.RichText
wrapMode: Text.WordWrap
onLinkActivated: function (link) { onLinkActivated: function (link) {
Qt.openUrlExternally(link) Qt.openUrlExternally(link);
} }
} }
} }

View File

@@ -11,15 +11,16 @@ import Logger 1.0
SplitView { SplitView {
id: root id: root
Layout.fillHeight: true
Layout.fillWidth: true
orientation: Qt.Vertical
// The path to the file to show in the text editor. // The path to the file to show in the text editor.
// This is updated by a signal caught within ClideProjectView. // This is updated by a signal caught within ClideProjectView.
// Initialized by the Default trait for the Rust QML singleton FileSystem. // Initialized by the Default trait for the Rust QML singleton FileSystem.
required property string filePath required property string filePath
Layout.fillHeight: true
Layout.fillWidth: true
orientation: Qt.Vertical
// Customized handle to drag between the Editor and the Console. // Customized handle to drag between the Editor and the Console.
handle: Rectangle { handle: Rectangle {
border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
@@ -34,12 +35,23 @@ SplitView {
} }
} }
} }
Component.onCompleted: {
// Show logging is working.
Logger.info("Info logs");
Logger.warn("Warning logs");
Logger.debug("Debug logs");
Logger.error("Error logs");
Logger.trace("Trace logs");
}
RowLayout { RowLayout {
// We use a flickable to synchronize the position of the editor and // We use a flickable to synchronize the position of the editor and
// the line numbers. This is necessary because the line numbers can // the line numbers. This is necessary because the line numbers can
// extend the available height. // extend the available height.
Flickable { Flickable {
id: lineNumbers id: lineNumbers
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: false Layout.fillWidth: false
// Calculating the width correctly is important as the number grows. // Calculating the width correctly is important as the number grows.
@@ -58,20 +70,22 @@ SplitView {
Repeater { Repeater {
id: repeatedLineNumbers id: repeatedLineNumbers
// TODO: Bug where text wrapping shows as new line number. // TODO: Bug where text wrapping shows as new line number.
model: textArea.lineCount model: textArea.lineCount
// This Item is used for each line number in the gutter. // This Item is used for each line number in the gutter.
delegate: Item { delegate: Item {
required property int index
// Calculates the height of each line in the text area. // Calculates the height of each line in the text area.
height: textArea.contentHeight / textArea.lineCount height: textArea.contentHeight / textArea.lineCount
width: parent.width width: parent.width
required property int index
// Show the line number. // Show the line number.
Label { Label {
id: numbers id: numbers
color: RustColors.linenumber color: RustColors.linenumber
font: textArea.font font: textArea.font
height: parent.height height: parent.height
@@ -79,6 +93,7 @@ SplitView {
text: parent.index + 1 text: parent.index + 1
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
width: parent.width - indicator.width width: parent.width - indicator.width
background: Rectangle { background: Rectangle {
color: RustColors.terminal_background color: RustColors.terminal_background
} }
@@ -86,6 +101,7 @@ SplitView {
// Draw edge along the right side of the line number. // Draw edge along the right side of the line number.
Rectangle { Rectangle {
id: indicator id: indicator
anchors.left: numbers.right anchors.left: numbers.right
color: RustColors.linenumber color: RustColors.linenumber
height: parent.height height: parent.height
@@ -97,6 +113,7 @@ SplitView {
} }
Flickable { Flickable {
id: editorFlickable id: editorFlickable
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
@@ -106,18 +123,18 @@ SplitView {
} }
ScrollBar.vertical: MyScrollBar { ScrollBar.vertical: MyScrollBar {
} }
TextArea.flickable: TextArea { TextArea.flickable: TextArea {
id: textArea id: textArea
antialiasing: true
focus: true focus: true
persistentSelection: true persistentSelection: true
antialiasing: true
selectByMouse: true selectByMouse: true
selectionColor: RustColors.editor_highlight
selectedTextColor: RustColors.editor_highlighted_text selectedTextColor: RustColors.editor_highlighted_text
selectionColor: RustColors.editor_highlight
text: FileSystem.readFile(root.filePath)
textFormat: Qt.AutoText textFormat: Qt.AutoText
wrapMode: TextArea.Wrap wrapMode: TextArea.Wrap
text: FileSystem.readFile(root.filePath)
onLinkActivated: function (link) { onLinkActivated: function (link) {
Qt.openUrlExternally(link); Qt.openUrlExternally(link);
@@ -155,6 +172,7 @@ SplitView {
} }
ClideLogger { ClideLogger {
id: areaConsole id: areaConsole
} }
// We use an inline component to customize the horizontal and vertical // We use an inline component to customize the horizontal and vertical
@@ -164,12 +182,13 @@ SplitView {
// Scroll bar gutter // Scroll bar gutter
background: Rectangle { background: Rectangle {
color: RustColors.scrollbar_gutter
implicitHeight: scrollBar.interactive ? 8 : 4 implicitHeight: scrollBar.interactive ? 8 : 4
implicitWidth: scrollBar.interactive ? 8 : 4 implicitWidth: scrollBar.interactive ? 8 : 4
color: RustColors.scrollbar_gutter
// Fade the scrollbar gutter when inactive. // Fade the scrollbar gutter when inactive.
opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.2 opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.2
Behavior on opacity { Behavior on opacity {
OpacityAnimator { OpacityAnimator {
duration: 500 duration: 500
@@ -179,20 +198,21 @@ SplitView {
// Scroll bar // Scroll bar
contentItem: Rectangle { contentItem: Rectangle {
implicitHeight: scrollBar.interactive ? 8 : 4
implicitWidth: scrollBar.interactive ? 8 : 4
// If we don't need a scrollbar, fallback to the gutter color. // If we don't need a scrollbar, fallback to the gutter color.
// If the scrollbar is required change it's color based on activity. // If the scrollbar is required change it's color based on activity.
color: scrollBar.size < 1.0 ? scrollBar.active ? RustColors.scrollbar_active : RustColors.scrollbar : RustColors.scrollbar_gutter color: scrollBar.size < 1.0 ? scrollBar.active ? RustColors.scrollbar_active : RustColors.scrollbar : RustColors.scrollbar_gutter
implicitHeight: scrollBar.interactive ? 8 : 4
implicitWidth: scrollBar.interactive ? 8 : 4
// Fade the scrollbar when inactive.
opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.35
// Smooth transition between color changes based on the state above. // Smooth transition between color changes based on the state above.
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: 1000 duration: 1000
} }
} }
// Fade the scrollbar when inactive.
opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.35
Behavior on opacity { Behavior on opacity {
OpacityAnimator { OpacityAnimator {
duration: 500 duration: 500
@@ -200,14 +220,4 @@ SplitView {
} }
} }
} }
Component.onCompleted: {
// Show logging is working.
Logger.info("Info logs")
Logger.warn("Warning logs")
Logger.debug("Debug logs")
Logger.error("Error logs")
Logger.trace("Trace logs")
}
} }

View File

@@ -9,47 +9,52 @@ import clide.module 1.0
import Logger 1.0 import Logger 1.0
Item { Item {
ListModel { id: model } ListModel {
id: model
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: "#111" color: "#111"
} }
ListView { ListView {
id: listView id: listView
anchors.fill: parent
model: model
clip: true
function getLogColor(level) { function getLogColor(level) {
switch (level) { switch (level) {
case "INFO": case "INFO":
return RustColors.info_log return RustColors.info_log;
case "DEBUG": case "DEBUG":
return RustColors.debug_log return RustColors.debug_log;
case "WARN": case "WARN":
return RustColors.warn_log return RustColors.warn_log;
case "ERROR": case "ERROR":
return RustColors.error_log return RustColors.error_log;
case "TRACE": case "TRACE":
return RustColors.trace_log return RustColors.trace_log;
default: default:
return RustColors.info_log return RustColors.info_log;
} }
} }
anchors.fill: parent
clip: true
model: model
delegate: Text { delegate: Text {
text: `[${level}] ${message}`
font.family: "monospace"
color: listView.getLogColor(level) color: listView.getLogColor(level)
font.family: "monospace"
text: `[${level}] ${message}`
} }
} }
Connections { Connections {
target: Logger
function onLogged(level, message) { function onLogged(level, message) {
model.append({ level, message }) model.append({
level,
message
});
} }
target: Logger
} }
} }

View File

@@ -10,32 +10,8 @@ import clide.module 1.0
MenuBar { MenuBar {
// Background for this MenuBar. // Background for this MenuBar.
background: Rectangle { background: Rectangle {
color: RustColors.menubar
border.color: RustColors.explorer_background border.color: RustColors.explorer_background
} color: RustColors.menubar
// Base settings for each Menu.
component ClideMenu : Menu {
background: Rectangle {
color: RustColors.explorer_background
implicitWidth: 100
radius: 2
}
}
// Base settings for each MenuItem.
component ClideMenuItem : MenuItem {
id: root
background: Rectangle {
color: root.hovered ? RustColors.hovered : RustColors.unhovered
radius: 1.0
}
contentItem: IconLabel {
color: "black"
font.family: "Helvetica"
text: root.text
}
} }
// //
@@ -70,6 +46,7 @@ MenuBar {
} }
ClideMenuItem { ClideMenuItem {
action: actionOpen action: actionOpen
onTriggered: FileSystem.setDirectory(FileSystem.filePath) onTriggered: FileSystem.setDirectory(FileSystem.filePath)
} }
ClideMenuItem { ClideMenuItem {
@@ -162,8 +139,8 @@ MenuBar {
// Help Menu // Help Menu
ClideAboutWindow { ClideAboutWindow {
id: clideAbout id: clideAbout
}
}
Action { Action {
id: actionDocumentation id: actionDocumentation
@@ -171,10 +148,11 @@ MenuBar {
} }
Action { Action {
id: actionAbout id: actionAbout
// Toggle the about window with the menu item is clicked.
onTriggered: clideAbout.visible = !clideAbout.visible
text: qsTr("&About") text: qsTr("&About")
// Toggle the about window with the menu item is clicked.
onTriggered: clideAbout.visible = !clideAbout.visible
} }
ClideMenu { ClideMenu {
title: qsTr("&Help") title: qsTr("&Help")
@@ -186,4 +164,28 @@ MenuBar {
action: actionAbout action: actionAbout
} }
} }
// Base settings for each Menu.
component ClideMenu: Menu {
background: Rectangle {
color: RustColors.explorer_background
implicitWidth: 100
radius: 2
}
}
// Base settings for each MenuItem.
component ClideMenuItem: MenuItem {
id: root
background: Rectangle {
color: root.hovered ? RustColors.hovered : RustColors.unhovered
radius: 1.0
}
contentItem: IconLabel {
color: "black"
font.family: "Helvetica"
text: root.text
}
}
} }

View File

@@ -22,6 +22,7 @@ SplitView {
// Customized handle to drag between the Navigation and the Editor. // Customized handle to drag between the Navigation and the Editor.
handle: Rectangle { handle: Rectangle {
id: verticalSplitHandle id: verticalSplitHandle
border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
implicitWidth: 8 implicitWidth: 8
@@ -37,65 +38,71 @@ SplitView {
Rectangle { Rectangle {
id: navigationView id: navigationView
color: RustColors.explorer_background
SplitView.fillHeight: true SplitView.fillHeight: true
SplitView.maximumWidth: 250
SplitView.minimumWidth: 0 SplitView.minimumWidth: 0
SplitView.preferredWidth: 200 SplitView.preferredWidth: 200
SplitView.maximumWidth: 250 color: RustColors.explorer_background
ColumnLayout { ColumnLayout {
spacing: 2 spacing: 2
// TODO: Make a ClideBreadCrumb element to support select parent paths as root // TODO: Make a ClideBreadCrumb element to support select parent paths as root
Rectangle { Rectangle {
width: navigationView.width
height: 25
color: RustColors.explorer_background color: RustColors.explorer_background
height: 25
width: navigationView.width
Text { Text {
id: breadCrumb id: breadCrumb
anchors.fill: parent anchors.fill: parent
text: clideTreeView.rootDirectory
color: RustColors.explorer_text color: RustColors.explorer_text
elide: Text.ElideLeft elide: Text.ElideLeft
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: clideTreeView.rootDirectory
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onSingleTapped: (eventPoint, button) => contextMenu.popup() onSingleTapped: (eventPoint, button) => contextMenu.popup()
} }
Menu { Menu {
id: contextMenu id: contextMenu
Action { Action {
text: qsTr("Reset root index") text: qsTr("Reset root index")
onTriggered: { onTriggered: {
Logger.log("Resetting root directory: " + clideTreeView.originalRootDirectory) Logger.log("Resetting root directory: " + clideTreeView.originalRootDirectory);
clideTreeView.rootDirectory = clideTreeView.originalRootDirectory clideTreeView.rootDirectory = clideTreeView.originalRootDirectory;
} }
} }
} }
} }
ClideTreeView { ClideTreeView {
id: clideTreeView id: clideTreeView
onFileClicked: path => clideEditor.filePath = path
width: navigationView.width
height: navigationView.height height: navigationView.height
// Path to the directory opened in the file explorer. // Path to the directory opened in the file explorer.
originalRootDirectory: root.projectDir originalRootDirectory: root.projectDir
rootDirectory: root.projectDir rootDirectory: root.projectDir
width: navigationView.width
onFileClicked: path => clideEditor.filePath = path
onRootDirectoryChanged: { onRootDirectoryChanged: {
Logger.log("Setting root directory: " + clideTreeView.rootDirectory) Logger.log("Setting root directory: " + clideTreeView.rootDirectory);
breadCrumb.text = clideTreeView.rootDirectory breadCrumb.text = clideTreeView.rootDirectory;
} }
} }
} }
} }
ClideEditor { ClideEditor {
id: clideEditor id: clideEditor
SplitView.fillWidth: true SplitView.fillWidth: true
// Provide a path to the file currently open in the text editor. // Provide a path to the file currently open in the text editor.

View File

@@ -11,141 +11,19 @@ import Logger 1.0
TreeView { TreeView {
id: fileSystemTreeView id: fileSystemTreeView
model: FileSystem
property int lastIndex: -1 property int lastIndex: -1
required property string originalRootDirectory required property string originalRootDirectory
property string rootDirectory property string rootDirectory
signal fileClicked(string filePath) signal fileClicked(string filePath)
rootIndex: FileSystem.setDirectory(fileSystemTreeView.rootDirectory)
leftMargin: 5
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds
clip: true clip: true
leftMargin: 5
// The delegate represents a single entry in the filesystem. model: FileSystem
delegate: TreeViewDelegate { rootIndex: FileSystem.setDirectory(fileSystemTreeView.rootDirectory)
id: treeDelegate
indentation: 12
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,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 576 512\"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d=\"M88.7 223.8L0 375.8 0 96C0 60.7 28.7 32 64 32l117.5 0c17 0 33.3 6.7 45.3 18.7l26.5 26.5c12 12 28.3 18.7 45.3 18.7L416 96c35.3 0 64 28.7 64 64l0 32-336 0c-22.8 0-43.8 12.1-55.3 31.8zm27.6 16.1C122.1 230 132.6 224 144 224l400 0c11.5 0 22 6.1 27.7 16.1s5.7 22.2-.1 32.1l-112 192C453.9 474 443.4 480 432 480L32 480c-11.5 0-22-6.1-27.7-16.1s-5.7-22.2 .1-32.1l112-192z\"/></svg>";
let folderClosed = "data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d=\"M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z\"/></svg>";
let file = "data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 384 512\"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d=\"M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 288c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128z\"/></svg>";
// If the item has children, it's a directory.
if (treeDelegate.hasChildren) {
return treeDelegate.expanded ?
folderOpen : folderClosed;
}
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
}
}
}
MultiEffect {
id: iconOverlay
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)
const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren;
if (isExpandedFolder)
return Qt.darker(RustColors.explorer_folder, 2)
else
return RustColors.explorer_folder
}
}
HoverHandler {
id: hoverHandler
acceptedDevices: PointerDevice.Mouse
}
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onSingleTapped: (eventPoint, button) => {
switch (button) {
case Qt.LeftButton:
fileSystemTreeView.toggleExpanded(treeDelegate.row)
// If this model item doesn't have children, it means it's
// representing a file.
if (!treeDelegate.hasChildren)
fileSystemTreeView.fileClicked(treeDelegate.filePath)
break;
case Qt.RightButton:
contextMenu.popup();
break;
}
}
}
Menu {
id: contextMenu
Action {
text: qsTr("Set as root index")
enabled: treeDelegate.hasChildren
onTriggered: {
Logger.debug("Setting new root directory: " + treeDelegate.filePath)
fileSystemTreeView.rootDirectory = treeDelegate.filePath
}
}
Action {
text: qsTr("Reset root index")
onTriggered: {
Logger.log("Resetting root directory: " + fileSystemTreeView.originalRootDirectory)
fileSystemTreeView.rootDirectory = fileSystemTreeView.originalRootDirectory
}
}
}
}
// Provide our own custom ScrollIndicator for the TreeView. // Provide our own custom ScrollIndicator for the TreeView.
ScrollIndicator.vertical: ScrollIndicator { ScrollIndicator.vertical: ScrollIndicator {
@@ -153,10 +31,9 @@ TreeView {
implicitWidth: 15 implicitWidth: 15
contentItem: Rectangle { contentItem: Rectangle {
implicitWidth: 6
implicitHeight: 6
color: RustColors.scrollbar color: RustColors.scrollbar
implicitHeight: 6
implicitWidth: 6
opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0 opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
Behavior on opacity { Behavior on opacity {
@@ -166,4 +43,122 @@ TreeView {
} }
} }
} }
// The delegate represents a single entry in the filesystem.
delegate: TreeViewDelegate {
id: treeDelegate
required property string fileName
required property url filePath
required property int index
implicitHeight: 25
implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250
indentation: 12
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")
opacity: hoverHandler.hovered ? 0.75 : 1.0
radius: 2.5
Behavior on color {
ColorAnimation {
duration: 300
}
}
}
contentItem: Text {
color: RustColors.explorer_text
text: treeDelegate.fileName
}
indicator: Image {
id: directoryIcon
function setSourceImage() {
let folderOpen = "data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 576 512\"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d=\"M88.7 223.8L0 375.8 0 96C0 60.7 28.7 32 64 32l117.5 0c17 0 33.3 6.7 45.3 18.7l26.5 26.5c12 12 28.3 18.7 45.3 18.7L416 96c35.3 0 64 28.7 64 64l0 32-336 0c-22.8 0-43.8 12.1-55.3 31.8zm27.6 16.1C122.1 230 132.6 224 144 224l400 0c11.5 0 22 6.1 27.7 16.1s5.7 22.2-.1 32.1l-112 192C453.9 474 443.4 480 432 480L32 480c-11.5 0-22-6.1-27.7-16.1s-5.7-22.2 .1-32.1l112-192z\"/></svg>";
let folderClosed = "data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d=\"M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z\"/></svg>";
let file = "data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 384 512\"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d=\"M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 288c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128z\"/></svg>";
// If the item has children, it's a directory.
if (treeDelegate.hasChildren) {
return treeDelegate.expanded ? folderOpen : folderClosed;
}
return file;
}
anchors.verticalCenter: parent.verticalCenter
antialiasing: true
asynchronous: true
fillMode: Image.PreserveAspectFit
smooth: true
source: setSourceImage()
sourceSize.height: 15
sourceSize.width: 15
x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation)
}
MultiEffect {
id: iconOverlay
anchors.fill: directoryIcon
brightness: 1.0
colorization: 1.0
colorizationColor: {
const isFile = !treeDelegate.hasChildren;
if (isFile)
return Qt.lighter(RustColors.explorer_folder, 2);
const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren;
if (isExpandedFolder)
return Qt.darker(RustColors.explorer_folder, 2);
else
return RustColors.explorer_folder;
}
source: directoryIcon
}
HoverHandler {
id: hoverHandler
acceptedDevices: PointerDevice.Mouse
}
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onSingleTapped: (eventPoint, button) => {
switch (button) {
case Qt.LeftButton:
fileSystemTreeView.toggleExpanded(treeDelegate.row);
// If this model item doesn't have children, it means it's
// representing a file.
if (!treeDelegate.hasChildren)
fileSystemTreeView.fileClicked(treeDelegate.filePath);
break;
case Qt.RightButton:
contextMenu.popup();
break;
}
}
}
Menu {
id: contextMenu
Action {
enabled: treeDelegate.hasChildren
text: qsTr("Set as root index")
onTriggered: {
Logger.debug("Setting new root directory: " + treeDelegate.filePath);
fileSystemTreeView.rootDirectory = treeDelegate.filePath;
}
}
Action {
text: qsTr("Reset root index")
onTriggered: {
Logger.log("Resetting root directory: " + fileSystemTreeView.originalRootDirectory);
fileSystemTreeView.rootDirectory = fileSystemTreeView.originalRootDirectory;
}
}
}
}
} }

View File

@@ -8,33 +8,28 @@ import QtQuick
QtObject { QtObject {
signal logged(string level, string message) signal logged(string level, string message)
function log(msg) {
console.log(msg)
logged("INFO", msg)
}
function info(msg) {
console.log(msg)
logged("INFO", msg)
}
function debug(msg) { function debug(msg) {
console.log(msg) console.log(msg);
logged("DEBUG", msg) logged("DEBUG", msg);
} }
function warn(msg) {
console.warn(msg)
logged("WARN", msg)
}
function error(msg) { function error(msg) {
console.error(msg) console.error(msg);
logged("ERROR", msg) logged("ERROR", msg);
}
function info(msg) {
console.log(msg);
logged("INFO", msg);
}
function log(msg) {
console.log(msg);
logged("INFO", msg);
} }
function trace(msg) { function trace(msg) {
console.log(msg) console.log(msg);
logged("TRACE", msg) logged("TRACE", msg);
}
function warn(msg) {
console.warn(msg);
logged("WARN", msg);
} }
} }

View File

@@ -12,14 +12,15 @@ import clide.module 1.0
ApplicationWindow { ApplicationWindow {
id: appWindow id: appWindow
required property string appContextPath
height: 800 height: 800
title: "CLIDE" title: "CLIDE"
visible: true visible: true
width: 1200 width: 1200
required property string appContextPath menuBar: ClideMenuBar {
}
menuBar: ClideMenuBar { }
MessageDialog { MessageDialog {
id: errorDialog id: errorDialog
@@ -28,6 +29,7 @@ ApplicationWindow {
} }
ClideProjectView { ClideProjectView {
id: clideProjectView id: clideProjectView
projectDir: appWindow.appContextPath projectDir: appWindow.appContextPath
} }
} }