Compare commits
9 Commits
a63acb18fc
...
app-contex
| Author | SHA1 | Date | |
|---|---|---|---|
| 7306e09181 | |||
| 99ad6be5f2 | |||
| ec5a369b84 | |||
| c2c66ab7c0 | |||
| b607a6daaa | |||
| dc778dd859 | |||
| c30b64525b | |||
| a75dad7985 | |||
| a431d02a0e |
@@ -1,6 +1,3 @@
|
|||||||
|
|
||||||
[build]
|
[build]
|
||||||
rustflags = [ "-C", "link-arg=-fuse-ld=lld", ]
|
rustflags = [ "-C", "link-arg=-fuse-ld=lld", ]
|
||||||
|
|
||||||
[env]
|
|
||||||
QMAKE="/opt/Qt/6.7.3/gcc_64/bin/qmake6"
|
|
||||||
LD_LIBRARY_PATH="/opt/Qt/6.7.3/gcc_64/lib"
|
|
||||||
|
|||||||
33
.qmllint.ini
33
.qmllint.ini
@@ -1,33 +0,0 @@
|
|||||||
[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
|
|
||||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -295,7 +295,6 @@ dependencies = [
|
|||||||
"cxx-qt",
|
"cxx-qt",
|
||||||
"cxx-qt-build",
|
"cxx-qt-build",
|
||||||
"cxx-qt-lib",
|
"cxx-qt-lib",
|
||||||
"devicons",
|
|
||||||
"dirs",
|
"dirs",
|
||||||
"edtui",
|
"edtui",
|
||||||
"log",
|
"log",
|
||||||
@@ -668,15 +667,6 @@ dependencies = [
|
|||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "devicons"
|
|
||||||
version = "0.6.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "830e47e2f330cf4fdd5a958dcef921b9523ffc21ab6713aa5e77ba2cce03904b"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ tui-logger = "0.18.1"
|
|||||||
edtui = "0.11.1"
|
edtui = "0.11.1"
|
||||||
strum = "0.27.2"
|
strum = "0.27.2"
|
||||||
uuid = { version = "1.19.0", features = ["v4"] }
|
uuid = { version = "1.19.0", features = ["v4"] }
|
||||||
devicons = "0.6.12"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
# The link_qt_object_files feature is required for statically linking Qt 6.
|
# The link_qt_object_files feature is required for statically linking Qt 6.
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -19,31 +19,6 @@ And of course, [Rust](https://www.rust-lang.org/tools/install).
|
|||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
This project requires at least Qt 6.7.3 To check your Qt version
|
|
||||||
|
|
||||||
```bash
|
|
||||||
qmake6 -query QT_VERSION
|
|
||||||
```
|
|
||||||
|
|
||||||
Use the [Qt Installer](https://www.qt.io/development/download) to download and install the Qt version of your choice.
|
|
||||||
If the installer is run with `sudo`, the default install location is `/opt/Qt`, otherwise Qt will be installed into your home directory.
|
|
||||||
|
|
||||||
**You must set the QMAKE variable before building clide**. This should be a path to `qmake6` binary installed on your system.
|
|
||||||
The following export is the default installation path for Qt 6.7 on Ubuntu 24.04
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export QMAKE=$HOME/Qt/6.7.3/gcc_64/bin/qmake6
|
|
||||||
export LD_LIBRARY_PATH=$HOME/Qt/6.7.3/gcc_64/lib
|
|
||||||
```
|
|
||||||
|
|
||||||
Though environment variables set using `export` will take precedence, these can also be set in [.cargo/config.toml](./.cargo/config.toml) for conveinence
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[env]
|
|
||||||
QMAKE="/opt/Qt/6.7.3/gcc_64/bin/qmake6"
|
|
||||||
LD_LIBRARY_PATH="/opt/Qt/6.7.3/gcc_64/lib"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
To install and run clide
|
To install and run clide
|
||||||
@@ -151,7 +126,6 @@ Some helpful links for reading up on QML if you're just getting started.
|
|||||||
* [All QML Controls Types](https://doc.qt.io/qt-6/qtquick-controls-qmlmodule.html)
|
* [All QML Controls Types](https://doc.qt.io/qt-6/qtquick-controls-qmlmodule.html)
|
||||||
* [KDAB CXX-Qt Book](https://kdab.github.io/cxx-qt/book/)
|
* [KDAB CXX-Qt Book](https://kdab.github.io/cxx-qt/book/)
|
||||||
* [github.com/KDAB/cxx-qt](https://github.com/KDAB/cxx-qt)
|
* [github.com/KDAB/cxx-qt](https://github.com/KDAB/cxx-qt)
|
||||||
* [QML and C++ Intergration](https://doc.qt.io/qt-6/qtqml-cppintegration-overview.html)
|
|
||||||
|
|
||||||
|
|
||||||
### Plugins
|
### Plugins
|
||||||
|
|||||||
9
build.rs
9
build.rs
@@ -7,15 +7,7 @@ fn main() {
|
|||||||
"qml/ClideTreeView.qml",
|
"qml/ClideTreeView.qml",
|
||||||
"qml/ClideProjectView.qml",
|
"qml/ClideProjectView.qml",
|
||||||
"qml/ClideEditor.qml",
|
"qml/ClideEditor.qml",
|
||||||
"qml/ClideEditorView.qml",
|
|
||||||
"qml/ClideMenuBar.qml",
|
"qml/ClideMenuBar.qml",
|
||||||
"qml/ClideLogger.qml",
|
|
||||||
"qml/Components/ClideScrollBar.qml",
|
|
||||||
"qml/Components/ClideHandle.qml",
|
|
||||||
"qml/Components/ClideMenu.qml",
|
|
||||||
"qml/Components/ClideMenuItem.qml",
|
|
||||||
"qml/Components/ClideBreadCrumbs.qml",
|
|
||||||
"qml/Logger/Logger.qml",
|
|
||||||
]))
|
]))
|
||||||
// Link Qt's Network library
|
// Link Qt's Network library
|
||||||
// - Qt Core is always linked
|
// - Qt Core is always linked
|
||||||
@@ -26,7 +18,6 @@ fn main() {
|
|||||||
.qt_module("Gui")
|
.qt_module("Gui")
|
||||||
.qt_module("Svg")
|
.qt_module("Svg")
|
||||||
.qt_module("Xml")
|
.qt_module("Xml")
|
||||||
.qrc("./resources.qrc")
|
|
||||||
.files(["src/gui/colors.rs", "src/gui/filesystem.rs"])
|
.files(["src/gui/colors.rs", "src/gui/filesystem.rs"])
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@@ -1,27 +1,21 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
// TODO: Header
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls.Basic
|
import QtQuick.Controls.Basic
|
||||||
|
|
||||||
import clide.module 1.0
|
import clide.module 1.0
|
||||||
import Logger 1.0
|
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
color: RustColors.gutter
|
|
||||||
// Create the window with no frame and keep it on top.
|
|
||||||
flags: Qt.Tool | Qt.FramelessWindowHint
|
|
||||||
height: 350
|
|
||||||
width: 450
|
width: 450
|
||||||
visible: root.active
|
height: 350
|
||||||
|
// Create the window with no frame and keep it on top.
|
||||||
|
flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
|
||||||
|
color: RustColors.gutter
|
||||||
|
|
||||||
// Hide the window when it loses focus.
|
// Hide the window when it loses focus.
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
Logger.debug("Setting active: " + root.active)
|
if (!active) {
|
||||||
if (!root.active) {
|
|
||||||
root.visible = false;
|
root.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,38 +25,48 @@ 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: "../icons/kilroy-256.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.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.margins: 20
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: logo.bottom
|
anchors.top: logo.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: 20
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
antialiasing: true
|
|
||||||
background: null
|
|
||||||
color: RustColors.editor_text
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
readOnly: true
|
|
||||||
selectedTextColor: RustColors.editor_highlighted_text
|
selectedTextColor: RustColors.editor_highlighted_text
|
||||||
selectionColor: RustColors.editor_highlight
|
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>")
|
horizontalAlignment: Text.AlignHCenter
|
||||||
textFormat: Text.RichText
|
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
|
wrapMode: Text.WordWrap
|
||||||
|
readOnly: true
|
||||||
|
antialiasing: true
|
||||||
|
background: null
|
||||||
|
|
||||||
onLinkActivated: function (link) {
|
onLinkActivated: function (link) {
|
||||||
Qt.openUrlExternally(link);
|
Qt.openUrlExternally(link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,136 +1,206 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import clide.module 1.0
|
import clide.module 1.0
|
||||||
import Logger 1.0
|
|
||||||
|
|
||||||
RowLayout {
|
SplitView {
|
||||||
// We use a flickable to synchronize the position of the editor and
|
id: root
|
||||||
// the line numbers. This is necessary because the line numbers can
|
Layout.fillHeight: true
|
||||||
// extend the available height.
|
Layout.fillWidth: true
|
||||||
Flickable {
|
orientation: Qt.Vertical
|
||||||
id: lineNumbers
|
|
||||||
|
|
||||||
Layout.fillHeight: true
|
// The path to the file to show in the text editor.
|
||||||
Layout.fillWidth: false
|
// This is updated by a signal caught within ClideProjectView.
|
||||||
// Calculating the width correctly is important as the number grows.
|
// Initialized by the Default trait for the Rust QML singleton FileSystem.
|
||||||
// We need to ensure space required to show N line number digits.
|
required property string filePath
|
||||||
// We use log10 to find how many digits are needed in a line number.
|
|
||||||
// log10(9) ~= .95; log10(10) = 1.0; log10(100) = 2.0 ...etc
|
|
||||||
// We +1 to ensure space for at least 1 digit, as floor(1.95) = 1.
|
|
||||||
// The +10 is additional spacing and can be adjusted.
|
|
||||||
Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10
|
|
||||||
contentY: editorFlickable.contentY
|
|
||||||
interactive: false
|
|
||||||
|
|
||||||
Column {
|
// Customized handle to drag between the Editor and the Console.
|
||||||
anchors.fill: parent
|
handle: Rectangle {
|
||||||
topPadding: textArea.topPadding
|
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
|
||||||
|
|
||||||
Repeater {
|
// Execute these behaviors when the color is changed.
|
||||||
id: repeatedLineNumbers
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
// We need to ensure space required to show N line number digits.
|
||||||
|
// We use log10 to find how many digits are needed in a line number.
|
||||||
|
// log10(9) ~= .95; log10(10) = 1.0; log10(100) = 2.0 ...etc
|
||||||
|
// We +1 to ensure space for at least 1 digit, as floor(1.95) = 1.
|
||||||
|
// The +10 is additional spacing and can be adjusted.
|
||||||
|
Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10
|
||||||
|
contentY: editorFlickable.contentY
|
||||||
|
interactive: false
|
||||||
|
|
||||||
// TODO: Bug where text wrapping shows as new line number.
|
Column {
|
||||||
model: textArea.lineCount
|
anchors.fill: parent
|
||||||
|
topPadding: textArea.topPadding
|
||||||
|
|
||||||
// This Item is used for each line number in the gutter.
|
Repeater {
|
||||||
delegate: Item {
|
id: repeatedLineNumbers
|
||||||
required property int index
|
// TODO: Bug where text wrapping shows as new line number.
|
||||||
|
model: textArea.lineCount
|
||||||
|
|
||||||
// Calculates the height of each line in the text area.
|
// This Item is used for each line number in the gutter.
|
||||||
height: textArea.contentHeight / textArea.lineCount
|
delegate: Item {
|
||||||
width: parent.width
|
// Calculates the height of each line in the text area.
|
||||||
|
height: textArea.contentHeight / textArea.lineCount
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
// Show the line number.
|
required property int index
|
||||||
Label {
|
|
||||||
id: numbers
|
|
||||||
|
|
||||||
color: RustColors.linenumber
|
// Show the line number.
|
||||||
font: textArea.font
|
Label {
|
||||||
height: parent.height
|
id: numbers
|
||||||
horizontalAlignment: Text.AlignLeft
|
color: RustColors.linenumber
|
||||||
text: parent.index + 1
|
font: textArea.font
|
||||||
verticalAlignment: Text.AlignVCenter
|
height: parent.height
|
||||||
width: parent.width - indicator.width
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
text: parent.index + 1
|
||||||
background: Rectangle {
|
verticalAlignment: Text.AlignVCenter
|
||||||
color: RustColors.terminal_background
|
width: parent.width - indicator.width
|
||||||
|
}
|
||||||
|
// Draw edge along the right side of the line number.
|
||||||
|
Rectangle {
|
||||||
|
id: indicator
|
||||||
|
anchors.left: numbers.right
|
||||||
|
color: RustColors.linenumber
|
||||||
|
height: parent.height
|
||||||
|
width: 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Draw edge along the right side of the line number.
|
|
||||||
Rectangle {
|
|
||||||
id: indicator
|
|
||||||
|
|
||||||
anchors.left: numbers.right
|
|
||||||
color: RustColors.linenumber
|
|
||||||
height: parent.height
|
|
||||||
width: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Flickable {
|
||||||
Flickable {
|
id: editorFlickable
|
||||||
id: editorFlickable
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
height: 650
|
||||||
|
|
||||||
Layout.fillHeight: true
|
ScrollBar.horizontal: MyScrollBar {
|
||||||
Layout.fillWidth: true
|
}
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
ScrollBar.vertical: MyScrollBar {
|
||||||
height: 650
|
|
||||||
|
|
||||||
ScrollBar.horizontal: ClideScrollBar {
|
|
||||||
}
|
|
||||||
ScrollBar.vertical: ClideScrollBar {
|
|
||||||
}
|
|
||||||
TextArea.flickable: TextArea {
|
|
||||||
id: textArea
|
|
||||||
|
|
||||||
antialiasing: true
|
|
||||||
focus: true
|
|
||||||
persistentSelection: true
|
|
||||||
selectByMouse: true
|
|
||||||
selectedTextColor: RustColors.editor_highlighted_text
|
|
||||||
selectionColor: RustColors.editor_highlight
|
|
||||||
text: FileSystem.readFile(root.filePath)
|
|
||||||
textFormat: Qt.AutoText
|
|
||||||
wrapMode: TextArea.Wrap
|
|
||||||
|
|
||||||
onLinkActivated: function (link) {
|
|
||||||
Qt.openUrlExternally(link);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle saving
|
TextArea.flickable: TextArea {
|
||||||
// Component.onCompleted: {
|
id: textArea
|
||||||
// if (Qt.application.arguments.length === 2)
|
focus: true
|
||||||
// textDocument.source = "file:" + Qt.application.arguments[1]
|
persistentSelection: true
|
||||||
// else
|
antialiasing: true
|
||||||
// textDocument.source = "qrc:/texteditor.html"
|
selectByMouse: true
|
||||||
// }
|
selectionColor: RustColors.editor_highlight
|
||||||
// textDocument.onStatusChanged: {
|
selectedTextColor: RustColors.editor_highlighted_text
|
||||||
// // a message lookup table using computed properties:
|
textFormat: Qt.AutoText
|
||||||
// // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
|
wrapMode: TextArea.Wrap
|
||||||
// const statusMessages = {
|
text: FileSystem.readFile(root.filePath)
|
||||||
// [ TextDocument.ReadError ]: qsTr("Failed to load “%1”"),
|
|
||||||
// [ TextDocument.WriteError ]: qsTr("Failed to save “%1”"),
|
onLinkActivated: function (link) {
|
||||||
// [ TextDocument.NonLocalFileError ]: qsTr("Not a local file: “%1”"),
|
Qt.openUrlExternally(link);
|
||||||
// }
|
}
|
||||||
// const err = statusMessages[textDocument.status]
|
|
||||||
// if (err) {
|
// TODO: Handle saving
|
||||||
// errorDialog.text = err.arg(textDocument.source)
|
// Component.onCompleted: {
|
||||||
// errorDialog.open()
|
// if (Qt.application.arguments.length === 2)
|
||||||
// }
|
// textDocument.source = "file:" + Qt.application.arguments[1]
|
||||||
// }
|
// else
|
||||||
|
// textDocument.source = "qrc:/texteditor.html"
|
||||||
|
// }
|
||||||
|
// textDocument.onStatusChanged: {
|
||||||
|
// // a message lookup table using computed properties:
|
||||||
|
// // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
|
||||||
|
// const statusMessages = {
|
||||||
|
// [ TextDocument.ReadError ]: qsTr("Failed to load “%1”"),
|
||||||
|
// [ TextDocument.WriteError ]: qsTr("Failed to save “%1”"),
|
||||||
|
// [ TextDocument.NonLocalFileError ]: qsTr("Not a local file: “%1”"),
|
||||||
|
// }
|
||||||
|
// const err = statusMessages[textDocument.status]
|
||||||
|
// if (err) {
|
||||||
|
// errorDialog.text = err.arg(textDocument.source)
|
||||||
|
// errorDialog.open()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
FontMetrics {
|
||||||
|
id: fontMetrics
|
||||||
|
|
||||||
|
font: textArea.font
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextArea {
|
||||||
|
id: areaConsole
|
||||||
|
|
||||||
|
height: 100
|
||||||
|
placeholderText: qsTr("Placeholder for bash terminal.")
|
||||||
|
placeholderTextColor: "white"
|
||||||
|
readOnly: true
|
||||||
|
wrapMode: TextArea.Wrap
|
||||||
|
background: Rectangle {
|
||||||
|
color: RustColors.editor_background
|
||||||
|
implicitHeight: 100
|
||||||
|
// border.color: control.enabled ? RustColors.active : RustColors.inactive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FontMetrics {
|
// Scroll bar
|
||||||
id: fontMetrics
|
contentItem: Rectangle {
|
||||||
|
implicitHeight: scrollBar.interactive ? 8 : 4
|
||||||
|
implicitWidth: scrollBar.interactive ? 8 : 4
|
||||||
|
|
||||||
font: textArea.font
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +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
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
orientation: Qt.Vertical
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
ClideEditor{
|
|
||||||
id: clideEditor
|
|
||||||
}
|
|
||||||
ClideLogger {
|
|
||||||
id: areaConsole
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +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 clide.module 1.0
|
|
||||||
import Logger 1.0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
ListModel {
|
|
||||||
id: model
|
|
||||||
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "#111"
|
|
||||||
}
|
|
||||||
ListView {
|
|
||||||
id: listView
|
|
||||||
|
|
||||||
function getLogColor(level) {
|
|
||||||
switch (level) {
|
|
||||||
case "INFO":
|
|
||||||
return RustColors.info_log;
|
|
||||||
case "DEBUG":
|
|
||||||
return RustColors.debug_log;
|
|
||||||
case "WARN":
|
|
||||||
return RustColors.warn_log;
|
|
||||||
case "ERROR":
|
|
||||||
return RustColors.error_log;
|
|
||||||
case "TRACE":
|
|
||||||
return RustColors.trace_log;
|
|
||||||
default:
|
|
||||||
return RustColors.info_log;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
clip: true
|
|
||||||
model: model
|
|
||||||
|
|
||||||
delegate: Text {
|
|
||||||
color: listView.getLogColor(level)
|
|
||||||
font.family: "monospace"
|
|
||||||
text: `[${level}] ${message}`
|
|
||||||
}
|
|
||||||
|
|
||||||
onCountChanged: Qt.callLater(positionViewAtEnd)
|
|
||||||
}
|
|
||||||
Connections {
|
|
||||||
function onLogged(level, message) {
|
|
||||||
model.append({
|
|
||||||
level,
|
|
||||||
message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
target: Logger
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +1,157 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
|
||||||
import clide.module 1.0
|
import clide.module 1.0
|
||||||
|
|
||||||
MenuBar {
|
MenuBar {
|
||||||
|
// Base settings for each Menu.
|
||||||
|
component ClideMenu : Menu {
|
||||||
|
background: Rectangle {
|
||||||
|
color: RustColors.menubar
|
||||||
|
implicitWidth: 100
|
||||||
|
radius: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base settings for each MenuItem.
|
||||||
|
component ClideMenuItem : MenuItem {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: root.hovered ? RustColors.hovered : RustColors.unhovered
|
||||||
|
radius: 2.5
|
||||||
|
}
|
||||||
|
contentItem: IconLabel {
|
||||||
|
color: "black"
|
||||||
|
font.family: "Helvetica"
|
||||||
|
text: root.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Background for this MenuBar.
|
// Background for this MenuBar.
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: RustColors.menubar
|
color: RustColors.menubar
|
||||||
radius: 20.0
|
border.color: RustColors.menubar_border
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// File Menu
|
// File Menu
|
||||||
|
Action {
|
||||||
|
id: actionNewProject
|
||||||
|
|
||||||
|
text: qsTr("&New Project...")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
id: actionOpen
|
||||||
|
|
||||||
|
text: qsTr("&Open...")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
id: actionSave
|
||||||
|
|
||||||
|
text: qsTr("&Save")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
id: actionExit
|
||||||
|
|
||||||
|
text: qsTr("&Exit")
|
||||||
|
|
||||||
|
onTriggered: Qt.quit()
|
||||||
|
}
|
||||||
ClideMenu {
|
ClideMenu {
|
||||||
title: qsTr("&File")
|
title: qsTr("&File")
|
||||||
|
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionNewProject
|
||||||
id: actionNewProject
|
|
||||||
|
|
||||||
text: qsTr("&New Project...")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionOpen
|
||||||
id: actionOpen
|
|
||||||
|
|
||||||
text: qsTr("&Open...")
|
|
||||||
}
|
|
||||||
|
|
||||||
onTriggered: FileSystem.setDirectory(FileSystem.filePath)
|
onTriggered: FileSystem.setDirectory(FileSystem.filePath)
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionSave
|
||||||
id: actionSave
|
|
||||||
|
|
||||||
text: qsTr("&Save")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
MenuSeparator {
|
MenuSeparator {
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
border.color: color
|
border.color: color
|
||||||
color: Qt.darker(RustColors.menubar, 1)
|
color: RustColors.menubar_border
|
||||||
implicitHeight: 3
|
implicitHeight: 3
|
||||||
implicitWidth: 200
|
implicitWidth: 200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionExit
|
||||||
id: actionExit
|
|
||||||
|
|
||||||
text: qsTr("&Exit")
|
|
||||||
|
|
||||||
onTriggered: Qt.quit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Edit Menu
|
// Edit Menu
|
||||||
|
Action {
|
||||||
|
id: actionUndo
|
||||||
|
|
||||||
|
text: qsTr("&Undo")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
id: actionRedo
|
||||||
|
|
||||||
|
text: qsTr("&Redo")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
id: actionCut
|
||||||
|
|
||||||
|
text: qsTr("&Cut")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
id: actionCopy
|
||||||
|
|
||||||
|
text: qsTr("&Copy")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
id: actionPaste
|
||||||
|
|
||||||
|
text: qsTr("&Paste")
|
||||||
|
}
|
||||||
ClideMenu {
|
ClideMenu {
|
||||||
title: qsTr("&Edit")
|
title: qsTr("&Edit")
|
||||||
|
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionUndo
|
||||||
id: actionUndo
|
|
||||||
|
|
||||||
text: qsTr("&Undo")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionRedo
|
||||||
id: actionRedo
|
|
||||||
|
|
||||||
text: qsTr("&Redo")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionCut
|
||||||
id: actionCut
|
|
||||||
|
|
||||||
text: qsTr("&Cut")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionCopy
|
||||||
id: actionCopy
|
|
||||||
|
|
||||||
text: qsTr("&Copy")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionPaste
|
||||||
id: actionPaste
|
|
||||||
|
|
||||||
text: qsTr("&Paste")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// View Menu
|
// View Menu
|
||||||
|
Action {
|
||||||
|
id: actionToolWindows
|
||||||
|
|
||||||
|
text: qsTr("&Tool Windows")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
id: actionAppearance
|
||||||
|
|
||||||
|
text: qsTr("&Appearance")
|
||||||
|
}
|
||||||
ClideMenu {
|
ClideMenu {
|
||||||
title: qsTr("&View")
|
title: qsTr("&View")
|
||||||
|
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionToolWindows
|
||||||
id: actionAppearance
|
|
||||||
|
|
||||||
text: qsTr("&Appearance")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionAppearance
|
||||||
id: actionToolWindows
|
|
||||||
|
|
||||||
text: qsTr("&Tool Windows")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,27 +159,28 @@ MenuBar {
|
|||||||
// Help Menu
|
// Help Menu
|
||||||
ClideAboutWindow {
|
ClideAboutWindow {
|
||||||
id: clideAbout
|
id: clideAbout
|
||||||
|
}
|
||||||
|
|
||||||
|
Action {
|
||||||
|
id: actionDocumentation
|
||||||
|
|
||||||
|
text: qsTr("&Documentation")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
id: actionAbout
|
||||||
|
// Toggle the about window with the menu item is clicked.
|
||||||
|
onTriggered: clideAbout.visible = !clideAbout.visible
|
||||||
|
|
||||||
|
text: qsTr("&About")
|
||||||
}
|
}
|
||||||
ClideMenu {
|
ClideMenu {
|
||||||
title: qsTr("&Help")
|
title: qsTr("&Help")
|
||||||
|
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionDocumentation
|
||||||
id: actionDocumentation
|
|
||||||
|
|
||||||
text: qsTr("&Documentation")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
ClideMenuItem {
|
||||||
action: Action {
|
action: actionAbout
|
||||||
id: actionAbout
|
|
||||||
|
|
||||||
text: qsTr("&About")
|
|
||||||
|
|
||||||
// Toggle the about window with the menu item is clicked.
|
|
||||||
onTriggered: clideAbout.visible = !clideAbout.visible
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import clide.module 1.0
|
import clide.module 1.0
|
||||||
import Logger 1.0
|
|
||||||
|
|
||||||
SplitView {
|
SplitView {
|
||||||
id: root
|
id: root
|
||||||
@@ -22,7 +17,6 @@ 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
|
||||||
@@ -38,51 +32,25 @@ 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
|
||||||
color: RustColors.explorer_background
|
SplitView.maximumWidth: 250
|
||||||
radius: 20
|
|
||||||
|
|
||||||
ColumnLayout {
|
StackLayout {
|
||||||
spacing: 2
|
anchors.fill: parent
|
||||||
|
|
||||||
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 {
|
ClideTreeView {
|
||||||
id: clideTreeView
|
id: clideTreeView
|
||||||
|
onFileClicked: path => root.projectDir = path
|
||||||
height: navigationView.height
|
|
||||||
|
|
||||||
// Path to the directory opened in the file explorer.
|
// Path to the directory opened in the file explorer.
|
||||||
originalRootDirectory: root.projectDir
|
|
||||||
rootDirectory: 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 {
|
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.
|
||||||
|
|||||||
@@ -1,167 +1,153 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import clide.module 1.0
|
import clide.module 1.0
|
||||||
import Logger 1.0
|
|
||||||
|
|
||||||
TreeView {
|
Rectangle {
|
||||||
id: fileSystemTreeView
|
id: root
|
||||||
|
color: RustColors.explorer_background
|
||||||
|
|
||||||
property int lastIndex: -1
|
required property string rootDirectory
|
||||||
required property string originalRootDirectory
|
|
||||||
property string rootDirectory
|
|
||||||
|
|
||||||
signal fileClicked(string filePath)
|
signal fileClicked(string filePath)
|
||||||
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
TreeView {
|
||||||
boundsMovement: Flickable.StopAtBounds
|
id: fileSystemTreeView
|
||||||
clip: true
|
anchors.margins: 15
|
||||||
leftMargin: 25
|
|
||||||
model: FileSystem
|
|
||||||
rootIndex: FileSystem.setDirectory(fileSystemTreeView.rootDirectory)
|
|
||||||
|
|
||||||
// Provide our own custom ScrollIndicator for the TreeView.
|
property int lastIndex: -1
|
||||||
ScrollIndicator.vertical: ScrollIndicator {
|
|
||||||
active: true
|
|
||||||
implicitWidth: 15
|
|
||||||
|
|
||||||
contentItem: Rectangle {
|
model: FileSystem
|
||||||
color: RustColors.scrollbar
|
anchors.fill: parent
|
||||||
implicitHeight: 6
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
implicitWidth: 6
|
boundsMovement: Flickable.StopAtBounds
|
||||||
opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
|
clip: true
|
||||||
|
|
||||||
Behavior on opacity {
|
Component.onCompleted: {
|
||||||
OpacityAnimator {
|
FileSystem.setDirectory(root.rootDirectory)
|
||||||
duration: 500
|
fileSystemTreeView.expandRecursively(0, -1)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
color: current ? RustColors.explorer_folder_open : "transparent"
|
|
||||||
radius: 2.5
|
|
||||||
}
|
|
||||||
contentItem: Text {
|
|
||||||
anchors.left: directoryIcon.right
|
|
||||||
anchors.leftMargin: 5
|
|
||||||
color: RustColors.explorer_text
|
|
||||||
text: treeDelegate.fileName
|
|
||||||
}
|
|
||||||
indicator: Label {
|
|
||||||
id: directoryIcon
|
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
antialiasing: true
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FontLoader {
|
// The delegate represents a single entry in the filesystem.
|
||||||
id: localFont
|
delegate: TreeViewDelegate {
|
||||||
|
id: treeDelegate
|
||||||
|
indentation: 8
|
||||||
|
implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250
|
||||||
|
implicitHeight: 25
|
||||||
|
|
||||||
source: "qrc:/fonts/saucecodepro-xlight.ttf"
|
required property int index
|
||||||
}
|
required property url filePath
|
||||||
Label {
|
required property string fileName
|
||||||
id: indicator
|
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
indicator: Image {
|
||||||
font.family: localFont.font.family
|
id: directoryIcon
|
||||||
font.pixelSize: 10
|
|
||||||
font.weight: localFont.font.weight
|
|
||||||
text: expanded ? "⮟" : "⮞"
|
|
||||||
visible: isTreeNode && hasChildren
|
|
||||||
x: padding + (depth * indentation)
|
|
||||||
}
|
|
||||||
MultiEffect {
|
|
||||||
id: iconOverlay
|
|
||||||
|
|
||||||
anchors.fill: directoryIcon
|
function setSourceImage() {
|
||||||
brightness: 1.0
|
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>";
|
||||||
colorization: 1.0
|
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>";
|
||||||
colorizationColor: {
|
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>";
|
||||||
const isFile = !treeDelegate.hasChildren;
|
// If the item has children, it's a directory.
|
||||||
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:
|
|
||||||
if (treeDelegate.hasChildren) {
|
if (treeDelegate.hasChildren) {
|
||||||
fileSystemTreeView.toggleExpanded(treeDelegate.row);
|
return treeDelegate.expanded ?
|
||||||
|
folderOpen : folderClosed;
|
||||||
} else {
|
} else {
|
||||||
// If this model item doesn't have children, it means it's representing a file.
|
return file
|
||||||
fileSystemTreeView.fileClicked(treeDelegate.filePath);
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case Qt.RightButton:
|
|
||||||
contextMenu.popup();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ClideMenu {
|
|
||||||
id: contextMenu
|
|
||||||
|
|
||||||
ClideMenuItem {
|
contentItem: Text {
|
||||||
action: Action {
|
text: treeDelegate.fileName
|
||||||
enabled: treeDelegate.hasChildren
|
color: RustColors.explorer_text
|
||||||
text: qsTr("Set root")
|
}
|
||||||
|
|
||||||
onTriggered: {
|
background: Rectangle {
|
||||||
Logger.debug("Setting new root directory: " + treeDelegate.filePath);
|
// TODO: Fix flickering from color transition on states here.
|
||||||
fileSystemTreeView.rootDirectory = treeDelegate.filePath;
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClideMenuItem {
|
|
||||||
action: Action {
|
|
||||||
text: qsTr("Reset root")
|
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: hoverHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
onSingleTapped: (eventPoint, button) => {
|
||||||
|
switch (button) {
|
||||||
|
case Qt.LeftButton:
|
||||||
|
fileSystemTreeView.toggleExpanded(treeDelegate.row)
|
||||||
|
fileSystemTreeView.lastIndex = treeDelegate.index
|
||||||
|
// If this model item doesn't have children, it means it's
|
||||||
|
// representing a file.
|
||||||
|
if (!treeDelegate.hasChildren)
|
||||||
|
root.fileClicked(treeDelegate.filePath)
|
||||||
|
break;
|
||||||
|
case Qt.RightButton:
|
||||||
|
if (treeDelegate.hasChildren)
|
||||||
|
contextMenu.popup();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: contextMenu
|
||||||
|
Action {
|
||||||
|
text: qsTr("Set as root index")
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Logger.log("Resetting root directory: " + fileSystemTreeView.originalRootDirectory);
|
console.log("Setting directory: " + treeDelegate.filePath)
|
||||||
fileSystemTreeView.rootDirectory = fileSystemTreeView.originalRootDirectory;
|
FileSystem.setDirectory(treeDelegate.filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
text: qsTr("Reset root index")
|
||||||
|
onTriggered: {
|
||||||
|
FileSystem.setDirectory("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide our own custom ScrollIndicator for the TreeView.
|
||||||
|
ScrollIndicator.vertical: ScrollIndicator {
|
||||||
|
active: true
|
||||||
|
implicitWidth: 15
|
||||||
|
|
||||||
|
contentItem: Rectangle {
|
||||||
|
implicitWidth: 6
|
||||||
|
implicitHeight: 6
|
||||||
|
|
||||||
|
color: RustColors.scrollbar
|
||||||
|
opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
OpacityAnimator {
|
||||||
|
duration: 500
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
selectionModel: ItemSelectionModel {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
import clide.module 1.0
|
|
||||||
import Logger 1.0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property var fullPaths: []
|
|
||||||
required property string path
|
|
||||||
property var segments: []
|
|
||||||
|
|
||||||
signal crumbClicked(string path)
|
|
||||||
|
|
||||||
function rebuildSegments() {
|
|
||||||
var 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
|
|
||||||
current += "/" + parts[i];
|
|
||||||
Logger.trace("Pushing path: " + parts[i] + " Current: " + current);
|
|
||||||
root.segments.push(parts[i]);
|
|
||||||
root.fullPaths.push(current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rep.model = root.segments;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.leftMargin: 20
|
|
||||||
height: breadcrumbRow.implicitHeight
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
Component.onCompleted: rebuildSegments()
|
|
||||||
onPathChanged: rebuildSegments()
|
|
||||||
|
|
||||||
Flow {
|
|
||||||
id: breadcrumbRow
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: rep
|
|
||||||
|
|
||||||
model: root.segments
|
|
||||||
|
|
||||||
delegate: Text {
|
|
||||||
id: linkText
|
|
||||||
|
|
||||||
required property string modelData
|
|
||||||
|
|
||||||
function getText() {
|
|
||||||
if (modelData === "/") {
|
|
||||||
return modelData;
|
|
||||||
}
|
|
||||||
Logger.trace("Getting valid text:" + modelData);
|
|
||||||
return modelData + "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
color: mouseArea.containsMouse ? "#2a7fff" : RustColors.explorer_text
|
|
||||||
font.underline: mouseArea.containsMouse
|
|
||||||
text: getText()
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
console.log("Breadcrumb clicked:", root.fullPaths[root.segments.indexOf(modelData)]);
|
|
||||||
root.crumbClicked(root.fullPaths[root.segments.indexOf(modelData)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
|
|
||||||
onSingleTapped: (eventPoint, button) => contextMenu.popup()
|
|
||||||
}
|
|
||||||
ClideMenu {
|
|
||||||
id: contextMenu
|
|
||||||
|
|
||||||
ClideMenuItem {
|
|
||||||
action: Action {
|
|
||||||
text: qsTr("Reset root")
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
Logger.log("Resetting root directory: " + clideTreeView.originalRootDirectory);
|
|
||||||
clideTreeView.rootDirectory = clideTreeView.originalRootDirectory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +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 clide.module 1.0
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
readonly property color currentColor: {
|
|
||||||
if (pressed) {
|
|
||||||
return RustColors.pressed;
|
|
||||||
} else if (hovered) {
|
|
||||||
return RustColors.hovered;
|
|
||||||
} else {
|
|
||||||
return "black";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
required property bool hovered
|
|
||||||
required property bool pressed
|
|
||||||
|
|
||||||
border.color: currentColor
|
|
||||||
color: currentColor
|
|
||||||
implicitHeight: 8
|
|
||||||
radius: 2.5
|
|
||||||
opacity: root.hovered ? 1.0 : 0.0
|
|
||||||
|
|
||||||
// Execute these behaviors when the color is changed.
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 500
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
OpacityAnimator {
|
|
||||||
duration: 500
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls.Basic
|
|
||||||
|
|
||||||
import clide.module 1.0
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
background: Rectangle {
|
|
||||||
border.color: RustColors.hovered
|
|
||||||
border.width: 10
|
|
||||||
color: RustColors.menubar
|
|
||||||
implicitWidth: 100
|
|
||||||
radius: 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls.Basic
|
|
||||||
|
|
||||||
import clide.module 1.0
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +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 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
ClideScrollBar ClideScrollBar.qml
|
|
||||||
ClideHandle ClideHandle.qml
|
|
||||||
ClideMenu ClideMenu.qml
|
|
||||||
ClideMenuItem ClideMenuItem.qml
|
|
||||||
ClideBreadCrumbs ClideBreadCrumbs.qml
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
pragma Singleton
|
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
signal logged(string level, string message)
|
|
||||||
|
|
||||||
function debug(msg) {
|
|
||||||
console.log(msg);
|
|
||||||
logged("DEBUG", msg);
|
|
||||||
}
|
|
||||||
function 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
singleton Logger 1.0 Logger.qml
|
|
||||||
15
qml/main.qml
15
qml/main.qml
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
@@ -12,24 +8,27 @@ 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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: RustColors.gutter
|
||||||
|
}
|
||||||
|
|
||||||
MessageDialog {
|
MessageDialog {
|
||||||
id: errorDialog
|
id: errorDialog
|
||||||
|
|
||||||
title: qsTr("Error")
|
title: qsTr("Error")
|
||||||
}
|
}
|
||||||
ClideProjectView {
|
ClideProjectView {
|
||||||
id: clideProjectView
|
|
||||||
|
|
||||||
projectDir: appWindow.appContextPath
|
projectDir: appWindow.appContextPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
<RCC>
|
|
||||||
<qresource prefix="/images">
|
|
||||||
<file alias="kilroy.png">resources/images/kilroy-256.png</file>
|
|
||||||
</qresource>
|
|
||||||
<qresource prefix="/fonts">
|
|
||||||
<file alias="saucecodepro.ttf">resources/SauceCodeProNerdFont-Black.ttf</file>
|
|
||||||
<file alias="saucecodepro-light.ttf">resources/SauceCodeProNerdFont-Light.ttf</file>
|
|
||||||
<file alias="saucecodepro-xlight.ttf">resources/SauceCodeProNerdFont-ExtraLight.ttf</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use crate::AppContext;
|
use crate::AppContext;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cxx_qt_lib::{QMapPair, QMapPair_QString_QVariant, QString, QVariant};
|
use cxx_qt_lib::{QMapPair, QMapPair_QString_QVariant, QString, QVariant};
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
#[cxx_qt::bridge]
|
#[cxx_qt::bridge]
|
||||||
|
|
||||||
pub mod qobject {
|
pub mod qobject {
|
||||||
unsafe extern "C++" {
|
unsafe extern "C++" {
|
||||||
include!("cxx-qt-lib/qcolor.h");
|
include!("cxx-qt-lib/qcolor.h");
|
||||||
@@ -36,12 +31,6 @@ pub mod qobject {
|
|||||||
#[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)]
|
||||||
#[qproperty(QColor, terminal_background)]
|
|
||||||
#[qproperty(QColor, info_log)]
|
|
||||||
#[qproperty(QColor, debug_log)]
|
|
||||||
#[qproperty(QColor, warn_log)]
|
|
||||||
#[qproperty(QColor, error_log)]
|
|
||||||
#[qproperty(QColor, trace_log)]
|
|
||||||
type RustColors = super::RustColorsImpl;
|
type RustColors = super::RustColorsImpl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,12 +60,6 @@ pub struct RustColorsImpl {
|
|||||||
explorer_background: QColor,
|
explorer_background: QColor,
|
||||||
explorer_folder: QColor,
|
explorer_folder: QColor,
|
||||||
explorer_folder_open: QColor,
|
explorer_folder_open: QColor,
|
||||||
terminal_background: QColor,
|
|
||||||
info_log: QColor,
|
|
||||||
debug_log: QColor,
|
|
||||||
warn_log: QColor,
|
|
||||||
error_log: QColor,
|
|
||||||
trace_log: QColor,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RustColorsImpl {
|
impl Default for RustColorsImpl {
|
||||||
@@ -85,7 +68,7 @@ impl Default for RustColorsImpl {
|
|||||||
hovered: QColor::try_from("#303234").unwrap(),
|
hovered: QColor::try_from("#303234").unwrap(),
|
||||||
unhovered: QColor::try_from("#3c3f41").unwrap(),
|
unhovered: QColor::try_from("#3c3f41").unwrap(),
|
||||||
pressed: QColor::try_from("#4b4f51").unwrap(),
|
pressed: QColor::try_from("#4b4f51").unwrap(),
|
||||||
menubar: QColor::try_from("#262626").unwrap(),
|
menubar: QColor::try_from("#3c3f41").unwrap(),
|
||||||
menubar_border: QColor::try_from("#575757").unwrap(),
|
menubar_border: QColor::try_from("#575757").unwrap(),
|
||||||
scrollbar: QColor::try_from("#4b4f51").unwrap(),
|
scrollbar: QColor::try_from("#4b4f51").unwrap(),
|
||||||
scrollbar_active: QColor::try_from("#4b4f51").unwrap(),
|
scrollbar_active: QColor::try_from("#4b4f51").unwrap(),
|
||||||
@@ -93,23 +76,17 @@ impl Default for RustColorsImpl {
|
|||||||
linenumber: QColor::try_from("#94989b").unwrap(),
|
linenumber: QColor::try_from("#94989b").unwrap(),
|
||||||
active: QColor::try_from("#a9acb0").unwrap(),
|
active: QColor::try_from("#a9acb0").unwrap(),
|
||||||
inactive: QColor::try_from("#FFF").unwrap(),
|
inactive: QColor::try_from("#FFF").unwrap(),
|
||||||
editor_background: QColor::try_from("#111111").unwrap(),
|
editor_background: QColor::try_from("#2b2b2b").unwrap(),
|
||||||
editor_text: QColor::try_from("#acaea3").unwrap(),
|
editor_text: QColor::try_from("#acaea3").unwrap(),
|
||||||
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_hovered: QColor::try_from("#4c5053").unwrap(),
|
explorer_hovered: QColor::try_from("#4c5053").unwrap(),
|
||||||
explorer_text: QColor::try_from("#FFF").unwrap(),
|
explorer_text: QColor::try_from("#3b3b3b").unwrap(),
|
||||||
explorer_text_selected: QColor::try_from("#262626").unwrap(),
|
explorer_text_selected: QColor::try_from("#8b8b8b").unwrap(),
|
||||||
explorer_background: QColor::try_from("#1E1F22").unwrap(),
|
explorer_background: QColor::try_from("#676c70").unwrap(),
|
||||||
explorer_folder: QColor::try_from("#54585b").unwrap(),
|
explorer_folder: QColor::try_from("#54585b").unwrap(),
|
||||||
explorer_folder_open: QColor::try_from("#393B40").unwrap(),
|
explorer_folder_open: QColor::try_from("#FFF").unwrap(),
|
||||||
terminal_background: QColor::try_from("#111111").unwrap(),
|
|
||||||
info_log: QColor::try_from("#C4FFFF").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(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use cxx_qt_lib::{QModelIndex, QString};
|
|
||||||
use devicons::FileIcon;
|
|
||||||
use dirs;
|
|
||||||
use log::warn;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
use syntect::easy::HighlightLines;
|
|
||||||
use syntect::highlighting::ThemeSet;
|
|
||||||
use syntect::html::{
|
|
||||||
IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet,
|
|
||||||
};
|
|
||||||
use syntect::parsing::SyntaxSet;
|
|
||||||
use syntect::util::LinesWithEndings;
|
|
||||||
|
|
||||||
#[cxx_qt::bridge]
|
#[cxx_qt::bridge]
|
||||||
pub mod qobject {
|
pub mod qobject {
|
||||||
unsafe extern "C++" {
|
unsafe extern "C++" {
|
||||||
@@ -35,6 +17,7 @@ pub mod qobject {
|
|||||||
#[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")]
|
||||||
type FileSystem = super::FileSystemImpl;
|
type FileSystem = super::FileSystemImpl;
|
||||||
|
|
||||||
#[inherit]
|
#[inherit]
|
||||||
@@ -53,15 +36,25 @@ pub mod qobject {
|
|||||||
#[qinvokable]
|
#[qinvokable]
|
||||||
#[cxx_name = "setDirectory"]
|
#[cxx_name = "setDirectory"]
|
||||||
fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex;
|
fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex;
|
||||||
|
|
||||||
#[qinvokable]
|
|
||||||
fn icon(self: Pin<&mut FileSystem>, path: &QString) -> QString;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement a provider for QFileSystemModel::setIconProvider for icons.
|
use cxx_qt_lib::{QModelIndex, QString};
|
||||||
|
use dirs;
|
||||||
|
use log::warn;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::BufRead;
|
||||||
|
use syntect::easy::HighlightFile;
|
||||||
|
use syntect::highlighting::ThemeSet;
|
||||||
|
use syntect::html::{
|
||||||
|
IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet,
|
||||||
|
};
|
||||||
|
use syntect::parsing::SyntaxSet;
|
||||||
|
|
||||||
|
// TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons.
|
||||||
pub struct FileSystemImpl {
|
pub struct FileSystemImpl {
|
||||||
file_path: QString,
|
file_path: QString,
|
||||||
|
root_index: QModelIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default is explicit to make the editor open this source file initially.
|
// Default is explicit to make the editor open this source file initially.
|
||||||
@@ -69,6 +62,7 @@ impl Default for FileSystemImpl {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
file_path: QString::from(file!()),
|
file_path: QString::from(file!()),
|
||||||
|
root_index: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,46 +72,42 @@ impl qobject::FileSystem {
|
|||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
return QString::default();
|
return QString::default();
|
||||||
}
|
}
|
||||||
let meta = fs::metadata(path.to_string())
|
if !fs::metadata(path.to_string())
|
||||||
.expect(format!("Failed to get file metadata {path:?}").as_str());
|
.expect(format!("Failed to get file metadata {path:?}").as_str())
|
||||||
if !meta.is_file() {
|
.is_file()
|
||||||
|
{
|
||||||
warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file");
|
warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file");
|
||||||
return QString::default();
|
return QString::default();
|
||||||
}
|
}
|
||||||
let path_str = path.to_string();
|
let ss = SyntaxSet::load_defaults_nonewlines();
|
||||||
if let Ok(lines) = fs::read_to_string(path_str.as_str()) {
|
let ts = ThemeSet::load_defaults();
|
||||||
let ss = SyntaxSet::load_defaults_nonewlines();
|
let theme = &ts.themes["base16-ocean.dark"];
|
||||||
let ts = ThemeSet::load_defaults();
|
|
||||||
let theme = &ts.themes["base16-ocean.dark"];
|
|
||||||
let lang = ss
|
|
||||||
.find_syntax_by_extension(
|
|
||||||
Path::new(path_str.as_str())
|
|
||||||
.extension()
|
|
||||||
.map(|s| s.to_str())
|
|
||||||
.unwrap_or_else(|| Some("md"))
|
|
||||||
.expect("Failed to get file extension"),
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|| ss.find_syntax_plain_text());
|
|
||||||
let mut highlighter = HighlightLines::new(lang, theme);
|
|
||||||
let (mut output, _bg) = start_highlighted_html_snippet(theme);
|
|
||||||
for line in LinesWithEndings::from(lines.as_str()) {
|
|
||||||
let regions = highlighter
|
|
||||||
.highlight_line(line, &ss)
|
|
||||||
.expect("Failed to highlight");
|
|
||||||
|
|
||||||
append_highlighted_html_for_styled_line(
|
let mut highlighter =
|
||||||
®ions[..],
|
HighlightFile::new(path.to_string(), &ss, theme).expect("Failed to create highlighter");
|
||||||
IncludeBackground::No,
|
let (mut output, _bg) = start_highlighted_html_snippet(theme);
|
||||||
&mut output,
|
let mut line = String::new();
|
||||||
)
|
while highlighter
|
||||||
.expect("Failed to insert highlighted html");
|
.reader
|
||||||
}
|
.read_line(&mut line)
|
||||||
|
.expect("Failed to read file.")
|
||||||
|
> 0
|
||||||
|
{
|
||||||
|
let regions = highlighter
|
||||||
|
.highlight_lines
|
||||||
|
.highlight_line(&line, &ss)
|
||||||
|
.expect("Failed to highlight");
|
||||||
|
|
||||||
output.push_str("</pre>\n");
|
append_highlighted_html_for_styled_line(
|
||||||
QString::from(output)
|
®ions[..],
|
||||||
} else {
|
IncludeBackground::Yes,
|
||||||
return QString::default();
|
&mut output,
|
||||||
|
)
|
||||||
|
.expect("Failed to insert highlighted html");
|
||||||
|
line.clear();
|
||||||
}
|
}
|
||||||
|
output.push_str("</pre>\n");
|
||||||
|
QString::from(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// There will never be more than one column.
|
// There will never be more than one column.
|
||||||
@@ -134,24 +124,14 @@ impl qobject::FileSystem {
|
|||||||
self.set_root_path(path)
|
self.set_root_path(path)
|
||||||
} 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.
|
||||||
let homedir = dirs::home_dir()
|
self.set_root_path(&QString::from(
|
||||||
.expect("Failed to get home directory")
|
dirs::home_dir()
|
||||||
.as_path()
|
.expect("Failed to get home directory")
|
||||||
.to_str()
|
.as_path()
|
||||||
.unwrap()
|
.to_str()
|
||||||
.to_string();
|
.unwrap()
|
||||||
self.set_root_path(&QString::from(homedir))
|
.to_string(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn icon(self: std::pin::Pin<&mut Self>, path: &QString) -> QString {
|
|
||||||
let str = path.to_string();
|
|
||||||
if Path::new(&str).is_dir() {
|
|
||||||
// Ensures directories are given a folder icon and not mistakenly resolved to a language.
|
|
||||||
// For example, a directory named `cpp` would otherwise return a C++ icon.
|
|
||||||
return QString::from(FileIcon::from("dir/").to_string())
|
|
||||||
}
|
|
||||||
let icon = FileIcon::from(str);
|
|
||||||
QString::from(icon.to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::{info, trace};
|
use log::{info, trace};
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
mod about;
|
mod about;
|
||||||
mod app;
|
mod app;
|
||||||
mod component;
|
mod component;
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use crate::tui::about::About;
|
use crate::tui::about::About;
|
||||||
use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger};
|
use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger};
|
||||||
use crate::tui::component::{Action, Component, Focus, FocusState, Visibility, VisibleState};
|
use crate::tui::component::{Action, Component, Focus, FocusState, Visibility, VisibleState};
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
#![allow(dead_code, unused_variables)]
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
use crate::tui::component::Focus::Inactive;
|
use crate::tui::component::Focus::Inactive;
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState};
|
use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState};
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use edtui::{
|
use edtui::{
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use crate::tui::component::{Action, Component, Focus, FocusState};
|
use crate::tui::component::{Action, Component, Focus, FocusState};
|
||||||
use crate::tui::editor::Editor;
|
use crate::tui::editor::Editor;
|
||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState};
|
use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState};
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState};
|
use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState};
|
||||||
use log::{LevelFilter, trace};
|
use log::{LevelFilter, trace};
|
||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
|
||||||
|
|
||||||
use crate::tui::component::{Action, Component, ComponentState, FocusState};
|
use crate::tui::component::{Action, Component, ComponentState, FocusState};
|
||||||
use crate::tui::menu_bar::MenuBarItemOption::{
|
use crate::tui::menu_bar::MenuBarItemOption::{
|
||||||
About, CloseTab, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger,
|
About, CloseTab, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger,
|
||||||
|
|||||||
Reference in New Issue
Block a user