Compare commits
9 Commits
e5b91eaed8
...
67bf82d0cb
| Author | SHA1 | Date | |
|---|---|---|---|
| 67bf82d0cb | |||
| 39377b32f0 | |||
| 176efb97b7 | |||
| 755066d847 | |||
| 773d7818b5 | |||
| 7e58e3ee03 | |||
| 0f50577d78 | |||
| 29024e3999 | |||
| 5af09485a3 |
33
.qmllint.ini
Normal file
33
.qmllint.ini
Normal file
@@ -0,0 +1,33 @@
|
||||
[General]
|
||||
DisableDefaultImports=false
|
||||
|
||||
[Warnings]
|
||||
AccessSingletonViaObject=warning
|
||||
AttachedPropertyReuse=disable
|
||||
BadSignalHandlerParameters=warning
|
||||
CompilerWarnings=disable
|
||||
Deprecated=warning
|
||||
DuplicatePropertyBinding=warning
|
||||
DuplicatedName=warning
|
||||
ImportFailure=warning
|
||||
IncompatibleType=warning
|
||||
InheritanceCycle=warning
|
||||
InvalidLintDirective=warning
|
||||
LintPluginWarnings=disable
|
||||
MissingProperty=warning
|
||||
MissingType=warning
|
||||
MultilineStrings=info
|
||||
NonListProperty=warning
|
||||
PrefixedImportType=warning
|
||||
PropertyAliasCycles=warning
|
||||
ReadOnlyProperty=warning
|
||||
RequiredProperty=warning
|
||||
RestrictedType=warning
|
||||
TopLevelComponent=warning
|
||||
UncreatableType=warning
|
||||
UnqualifiedAccess=warning
|
||||
UnresolvedType=warning
|
||||
UnusedImports=info
|
||||
UseProperFunction=warning
|
||||
VarUsedBeforeDeclaration=warning
|
||||
WithStatement=warning
|
||||
2
build.rs
2
build.rs
@@ -9,6 +9,8 @@ fn main() {
|
||||
"qml/ClideEditor.qml",
|
||||
"qml/ClideMenuBar.qml",
|
||||
"qml/ClideLogger.qml",
|
||||
"qml/Components/ClideScrollBar.qml",
|
||||
"qml/Components/ClideHandle.qml",
|
||||
"qml/Logger/Logger.qml",
|
||||
]))
|
||||
// Link Qt's Network library
|
||||
|
||||
@@ -6,18 +6,22 @@ import QtQuick
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
import clide.module 1.0
|
||||
import Logger 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
width: 450
|
||||
height: 350
|
||||
// Create the window with no frame and keep it on top.
|
||||
flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
|
||||
|
||||
color: RustColors.gutter
|
||||
// Create the window with no frame and keep it on top.
|
||||
flags: Qt.Tool | Qt.FramelessWindowHint
|
||||
height: 350
|
||||
width: 450
|
||||
visible: root.active
|
||||
|
||||
// Hide the window when it loses focus.
|
||||
onActiveChanged: {
|
||||
if (!active) {
|
||||
Logger.debug("Setting active: " + root.active)
|
||||
if (!root.active) {
|
||||
root.visible = false;
|
||||
}
|
||||
}
|
||||
@@ -27,48 +31,38 @@ ApplicationWindow {
|
||||
id: logo
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 20
|
||||
anchors.right: parent.right
|
||||
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
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
source: "qrc:/images/kilroy.png"
|
||||
sourceSize.height: 80
|
||||
sourceSize.width: 80
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
anchors.top: logo.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 20
|
||||
anchors.right: parent.right
|
||||
anchors.top: logo.bottom
|
||||
|
||||
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
|
||||
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) {
|
||||
Qt.openUrlExternally(link)
|
||||
Qt.openUrlExternally(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,35 +11,38 @@ import Logger 1.0
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
orientation: Qt.Vertical
|
||||
|
||||
// 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.
|
||||
required property string filePath
|
||||
|
||||
// Customized handle to drag between the Editor and the Console.
|
||||
handle: Rectangle {
|
||||
border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
|
||||
color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
|
||||
implicitHeight: 8
|
||||
radius: 2.5
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
orientation: Qt.Vertical
|
||||
|
||||
// Execute these behaviors when the color is changed.
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 400
|
||||
}
|
||||
// Customized handle to drag between the Editor and the Console.
|
||||
handle: ClideHandle {
|
||||
pressed: SplitHandle.pressed
|
||||
hovered: SplitHandle.hovered
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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.
|
||||
Flickable {
|
||||
id: lineNumbers
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: false
|
||||
// Calculating the width correctly is important as the number grows.
|
||||
@@ -58,20 +61,22 @@ SplitView {
|
||||
|
||||
Repeater {
|
||||
id: repeatedLineNumbers
|
||||
|
||||
// TODO: Bug where text wrapping shows as new line number.
|
||||
model: textArea.lineCount
|
||||
|
||||
// This Item is used for each line number in the gutter.
|
||||
delegate: Item {
|
||||
required property int index
|
||||
|
||||
// Calculates the height of each line in the text area.
|
||||
height: textArea.contentHeight / textArea.lineCount
|
||||
width: parent.width
|
||||
|
||||
required property int index
|
||||
|
||||
// Show the line number.
|
||||
Label {
|
||||
id: numbers
|
||||
|
||||
color: RustColors.linenumber
|
||||
font: textArea.font
|
||||
height: parent.height
|
||||
@@ -79,6 +84,7 @@ SplitView {
|
||||
text: parent.index + 1
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
width: parent.width - indicator.width
|
||||
|
||||
background: Rectangle {
|
||||
color: RustColors.terminal_background
|
||||
}
|
||||
@@ -86,6 +92,7 @@ SplitView {
|
||||
// Draw edge along the right side of the line number.
|
||||
Rectangle {
|
||||
id: indicator
|
||||
|
||||
anchors.left: numbers.right
|
||||
color: RustColors.linenumber
|
||||
height: parent.height
|
||||
@@ -97,27 +104,28 @@ SplitView {
|
||||
}
|
||||
Flickable {
|
||||
id: editorFlickable
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
height: 650
|
||||
|
||||
ScrollBar.horizontal: MyScrollBar {
|
||||
ScrollBar.horizontal: ClideScrollBar {
|
||||
}
|
||||
ScrollBar.vertical: MyScrollBar {
|
||||
ScrollBar.vertical: ClideScrollBar {
|
||||
}
|
||||
|
||||
TextArea.flickable: TextArea {
|
||||
id: textArea
|
||||
|
||||
antialiasing: true
|
||||
focus: true
|
||||
persistentSelection: true
|
||||
antialiasing: true
|
||||
selectByMouse: true
|
||||
selectionColor: RustColors.editor_highlight
|
||||
selectedTextColor: RustColors.editor_highlighted_text
|
||||
selectionColor: RustColors.editor_highlight
|
||||
text: FileSystem.readFile(root.filePath)
|
||||
textFormat: Qt.AutoText
|
||||
wrapMode: TextArea.Wrap
|
||||
text: FileSystem.readFile(root.filePath)
|
||||
|
||||
onLinkActivated: function (link) {
|
||||
Qt.openUrlExternally(link);
|
||||
@@ -155,56 +163,6 @@ SplitView {
|
||||
}
|
||||
ClideLogger {
|
||||
id: areaConsole
|
||||
}
|
||||
|
||||
// We use an inline component to customize the horizontal and vertical
|
||||
// scroll-bars. This is convenient when the component is only used in one file.
|
||||
component MyScrollBar: ScrollBar {
|
||||
id: scrollBar
|
||||
|
||||
// Scroll bar gutter
|
||||
background: Rectangle {
|
||||
implicitHeight: scrollBar.interactive ? 8 : 4
|
||||
implicitWidth: scrollBar.interactive ? 8 : 4
|
||||
color: RustColors.scrollbar_gutter
|
||||
|
||||
// Fade the scrollbar gutter when inactive.
|
||||
opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.2
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll bar
|
||||
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 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
|
||||
// Smooth transition between color changes based on the state above.
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
// Fade the scrollbar when inactive.
|
||||
opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.35
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
// Show logging is working.
|
||||
Logger.debug("Debug console ready")
|
||||
Logger.warn("Warnings show up too")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,50 +9,54 @@ import clide.module 1.0
|
||||
import Logger 1.0
|
||||
|
||||
Item {
|
||||
ListModel { id: model }
|
||||
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;
|
||||
return RustColors.info_log;
|
||||
case "DEBUG":
|
||||
return RustColors.debug_log
|
||||
break;
|
||||
return RustColors.debug_log;
|
||||
case "WARN":
|
||||
return RustColors.warn_log
|
||||
break;
|
||||
return RustColors.warn_log;
|
||||
case "ERROR":
|
||||
return RustColors.error_log
|
||||
break;
|
||||
return RustColors.error_log;
|
||||
case "TRACE":
|
||||
return RustColors.trace_log;
|
||||
default:
|
||||
return RustColors.info_log
|
||||
break;
|
||||
return RustColors.info_log;
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
model: model
|
||||
|
||||
delegate: Text {
|
||||
text: `[${level}] ${message}`
|
||||
font.family: "monospace"
|
||||
color: listView.getLogColor(level)
|
||||
}
|
||||
font.family: "monospace"
|
||||
text: `[${level}] ${message}`
|
||||
}
|
||||
|
||||
onCountChanged: Qt.callLater(positionViewAtEnd)
|
||||
}
|
||||
Connections {
|
||||
target: Logger
|
||||
function onLogged(level, message) {
|
||||
model.append({ level, message })
|
||||
}
|
||||
model.append({
|
||||
level,
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
target: Logger
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,32 +10,8 @@ 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.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
|
||||
}
|
||||
color: RustColors.menubar
|
||||
}
|
||||
|
||||
//
|
||||
@@ -70,6 +46,7 @@ MenuBar {
|
||||
}
|
||||
ClideMenuItem {
|
||||
action: actionOpen
|
||||
|
||||
onTriggered: FileSystem.setDirectory(FileSystem.filePath)
|
||||
}
|
||||
ClideMenuItem {
|
||||
@@ -162,8 +139,8 @@ MenuBar {
|
||||
// Help Menu
|
||||
ClideAboutWindow {
|
||||
id: clideAbout
|
||||
}
|
||||
|
||||
}
|
||||
Action {
|
||||
id: actionDocumentation
|
||||
|
||||
@@ -171,10 +148,11 @@ MenuBar {
|
||||
}
|
||||
Action {
|
||||
id: actionAbout
|
||||
// Toggle the about window with the menu item is clicked.
|
||||
onTriggered: clideAbout.visible = !clideAbout.visible
|
||||
|
||||
text: qsTr("&About")
|
||||
|
||||
// Toggle the about window with the menu item is clicked.
|
||||
onTriggered: clideAbout.visible = !clideAbout.visible
|
||||
}
|
||||
ClideMenu {
|
||||
title: qsTr("&Help")
|
||||
@@ -186,4 +164,28 @@ MenuBar {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ SplitView {
|
||||
// Customized handle to drag between the Navigation and the Editor.
|
||||
handle: Rectangle {
|
||||
id: verticalSplitHandle
|
||||
|
||||
border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
|
||||
color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
|
||||
implicitWidth: 8
|
||||
@@ -37,65 +38,71 @@ SplitView {
|
||||
|
||||
Rectangle {
|
||||
id: navigationView
|
||||
color: RustColors.explorer_background
|
||||
|
||||
SplitView.fillHeight: true
|
||||
SplitView.maximumWidth: 250
|
||||
SplitView.minimumWidth: 0
|
||||
SplitView.preferredWidth: 200
|
||||
SplitView.maximumWidth: 250
|
||||
color: RustColors.explorer_background
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
|
||||
// TODO: Make a ClideBreadCrumb element to support select parent paths as root
|
||||
Rectangle {
|
||||
width: navigationView.width
|
||||
height: 25
|
||||
color: RustColors.explorer_background
|
||||
height: 25
|
||||
width: navigationView.width
|
||||
|
||||
Text {
|
||||
id: breadCrumb
|
||||
|
||||
anchors.fill: parent
|
||||
text: clideTreeView.rootDirectory
|
||||
color: RustColors.explorer_text
|
||||
elide: Text.ElideLeft
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: clideTreeView.rootDirectory
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
|
||||
onSingleTapped: (eventPoint, button) => contextMenu.popup()
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: contextMenu
|
||||
|
||||
Action {
|
||||
text: qsTr("Reset root index")
|
||||
onTriggered: {
|
||||
Logger.log("Resetting root directory: " + clideTreeView.originalRootDirectory)
|
||||
clideTreeView.rootDirectory = clideTreeView.originalRootDirectory
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
Logger.log("Resetting root directory: " + clideTreeView.originalRootDirectory);
|
||||
clideTreeView.rootDirectory = clideTreeView.originalRootDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ClideTreeView {
|
||||
id: clideTreeView
|
||||
onFileClicked: path => clideEditor.filePath = path
|
||||
width: navigationView.width
|
||||
|
||||
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(clideTreeView.rootDirectory)
|
||||
breadCrumb.text = clideTreeView.rootDirectory
|
||||
Logger.log("Setting root directory: " + clideTreeView.rootDirectory);
|
||||
breadCrumb.text = clideTreeView.rootDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ClideEditor {
|
||||
id: clideEditor
|
||||
|
||||
SplitView.fillWidth: true
|
||||
|
||||
// Provide a path to the file currently open in the text editor.
|
||||
|
||||
@@ -11,142 +11,19 @@ import Logger 1.0
|
||||
|
||||
TreeView {
|
||||
id: fileSystemTreeView
|
||||
model: FileSystem
|
||||
|
||||
property int lastIndex: -1
|
||||
|
||||
required property string originalRootDirectory
|
||||
property string rootDirectory
|
||||
|
||||
signal fileClicked(string filePath)
|
||||
|
||||
rootIndex: FileSystem.setDirectory(fileSystemTreeView.rootDirectory)
|
||||
leftMargin: 5
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
boundsMovement: Flickable.StopAtBounds
|
||||
clip: true
|
||||
|
||||
// The delegate represents a single entry in the filesystem.
|
||||
delegate: TreeViewDelegate {
|
||||
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;
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
leftMargin: 5
|
||||
model: FileSystem
|
||||
rootIndex: FileSystem.setDirectory(fileSystemTreeView.rootDirectory)
|
||||
|
||||
// Provide our own custom ScrollIndicator for the TreeView.
|
||||
ScrollIndicator.vertical: ScrollIndicator {
|
||||
@@ -154,10 +31,9 @@ TreeView {
|
||||
implicitWidth: 15
|
||||
|
||||
contentItem: Rectangle {
|
||||
implicitWidth: 6
|
||||
implicitHeight: 6
|
||||
|
||||
color: RustColors.scrollbar
|
||||
implicitHeight: 6
|
||||
implicitWidth: 6
|
||||
opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -167,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
qml/Components/ClideHandle.qml
Normal file
34
qml/Components/ClideHandle.qml
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 clide.module 1.0
|
||||
|
||||
Rectangle {
|
||||
readonly property color currentColor: {
|
||||
if (pressed) {
|
||||
return RustColors.pressed;
|
||||
} else if (hovered) {
|
||||
return RustColors.hovered;
|
||||
} else {
|
||||
return RustColors.gutter;
|
||||
}
|
||||
}
|
||||
required property bool hovered
|
||||
required property bool pressed
|
||||
|
||||
border.color: currentColor
|
||||
color: currentColor
|
||||
implicitHeight: 8
|
||||
radius: 2.5
|
||||
|
||||
// Execute these behaviors when the color is changed.
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
71
qml/Components/ClideScrollBar.qml
Normal file
71
qml/Components/ClideScrollBar.qml
Normal file
@@ -0,0 +1,71 @@
|
||||
// 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 clide.module 1.0
|
||||
|
||||
ScrollBar {
|
||||
id: scrollBar
|
||||
|
||||
// Height, opacitiy, width
|
||||
property int h: scrollBar.interactive ? 4 * 2 : 4
|
||||
property int o: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
|
||||
property int w: scrollBar.interactive ? 4 * 2 : 4
|
||||
|
||||
// Scroll bar gutter
|
||||
background: Rectangle {
|
||||
id: gutter
|
||||
|
||||
color: RustColors.scrollbar_gutter
|
||||
implicitHeight: scrollBar.h
|
||||
implicitWidth: scrollBar.w
|
||||
|
||||
// Fade the scrollbar gutter when inactive.
|
||||
opacity: scrollBar.o
|
||||
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll bar
|
||||
contentItem: Rectangle {
|
||||
readonly property color barColor: {
|
||||
if (scrollBar.size < 1.0) {
|
||||
// If the scrollbar is required change it's color based on activity.
|
||||
if (scrollBar.active) {
|
||||
return RustColors.scrollbar_active;
|
||||
} else {
|
||||
return RustColors.scrollbar;
|
||||
}
|
||||
} else {
|
||||
// If we don't need a scrollbar, fallback to the gutter color.
|
||||
return gutter.color;
|
||||
}
|
||||
}
|
||||
|
||||
color: barColor
|
||||
implicitHeight: scrollBar.h
|
||||
implicitWidth: scrollBar.w
|
||||
|
||||
// Fade the scrollbar when inactive.
|
||||
opacity: scrollBar.o
|
||||
|
||||
// Smooth transition between color changes based on the state above.
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
qml/Components/qmldir
Normal file
2
qml/Components/qmldir
Normal file
@@ -0,0 +1,2 @@
|
||||
ClideScrollBar ClideScrollBar.qml
|
||||
ClideHandle ClideHandle.qml
|
||||
@@ -8,23 +8,28 @@ 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)
|
||||
console.log(msg);
|
||||
logged("DEBUG", msg);
|
||||
}
|
||||
|
||||
function warn(msg) {
|
||||
console.warn(msg)
|
||||
logged("WARN", msg)
|
||||
}
|
||||
|
||||
function error(msg) {
|
||||
console.error(msg)
|
||||
logged("ERROR", msg)
|
||||
console.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) {
|
||||
console.log(msg);
|
||||
logged("TRACE", msg);
|
||||
}
|
||||
function warn(msg) {
|
||||
console.warn(msg);
|
||||
logged("WARN", msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,15 @@ import clide.module 1.0
|
||||
ApplicationWindow {
|
||||
id: appWindow
|
||||
|
||||
required property string appContextPath
|
||||
|
||||
height: 800
|
||||
title: "CLIDE"
|
||||
visible: true
|
||||
width: 1200
|
||||
|
||||
required property string appContextPath
|
||||
|
||||
menuBar: ClideMenuBar { }
|
||||
menuBar: ClideMenuBar {
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: errorDialog
|
||||
@@ -28,6 +29,7 @@ ApplicationWindow {
|
||||
}
|
||||
ClideProjectView {
|
||||
id: clideProjectView
|
||||
|
||||
projectDir: appWindow.appContextPath
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ pub mod qobject {
|
||||
#[qproperty(QColor, debug_log)]
|
||||
#[qproperty(QColor, warn_log)]
|
||||
#[qproperty(QColor, error_log)]
|
||||
#[qproperty(QColor, trace_log)]
|
||||
type RustColors = super::RustColorsImpl;
|
||||
}
|
||||
}
|
||||
@@ -75,6 +76,7 @@ pub struct RustColorsImpl {
|
||||
debug_log: QColor,
|
||||
warn_log: QColor,
|
||||
error_log: QColor,
|
||||
trace_log: QColor,
|
||||
}
|
||||
|
||||
impl Default for RustColorsImpl {
|
||||
@@ -104,9 +106,10 @@ impl Default for RustColorsImpl {
|
||||
explorer_folder_open: QColor::try_from("#2b2b2b").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(),
|
||||
debug_log: QColor::try_from("#9148AF").unwrap(),
|
||||
warn_log: QColor::try_from("#C4A958").unwrap(),
|
||||
error_log: QColor::try_from("#ff5555").unwrap(),
|
||||
trace_log: QColor::try_from("#ffaa00").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user