Compare commits
41 Commits
shared-mod
...
gui
| Author | SHA1 | Date | |
|---|---|---|---|
| e3bf484a5d | |||
| bedd510b0a | |||
| a30ea2b457 | |||
| 52ceda5b06 | |||
| a37fd74d32 | |||
| 6ed4ca11f4 | |||
| 41fab2847d | |||
| c76529b3cb | |||
| 5f5b1a4d39 | |||
| 4bb4ce1882 | |||
| 344efc0042 | |||
| a63acb18fc | |||
| f918d65888 | |||
| aa42ec6072 | |||
| 6f2a655497 | |||
| c0f38b531d | |||
| 4aad91416e | |||
| ff1b5ab2e6 | |||
| 491087a6c1 | |||
| c170b3b20d | |||
| 125041f469 | |||
| 67bf82d0cb | |||
| 39377b32f0 | |||
| 176efb97b7 | |||
| 755066d847 | |||
| 773d7818b5 | |||
| 7e58e3ee03 | |||
| 0f50577d78 | |||
| 29024e3999 | |||
| 5af09485a3 | |||
| e5b91eaed8 | |||
| be383869b2 | |||
| b9eee50e52 | |||
| 4cc43916cb | |||
| a5bed9ed2c | |||
| 0fac2b71ab | |||
| db2f878018 | |||
| 048d40eb83 | |||
| c70bba16e4 | |||
| 325cf285fc | |||
| aa8590cd5c |
@@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
[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
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
|
||||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -295,6 +295,7 @@ dependencies = [
|
|||||||
"cxx-qt",
|
"cxx-qt",
|
||||||
"cxx-qt-build",
|
"cxx-qt-build",
|
||||||
"cxx-qt-lib",
|
"cxx-qt-lib",
|
||||||
|
"devicons",
|
||||||
"dirs",
|
"dirs",
|
||||||
"edtui",
|
"edtui",
|
||||||
"log",
|
"log",
|
||||||
@@ -667,6 +668,15 @@ 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,6 +18,7 @@ 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.
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -19,6 +19,31 @@ 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
|
||||||
@@ -82,6 +107,8 @@ cargo run
|
|||||||
clide
|
clide
|
||||||
```
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
It's recommended to use RustRover or Qt Creator for development.
|
It's recommended to use RustRover or Qt Creator for development.
|
||||||
@@ -126,6 +153,7 @@ 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
|
||||||
|
|||||||
20
build.rs
20
build.rs
@@ -2,12 +2,21 @@ use cxx_qt_build::{CxxQtBuilder, QmlModule};
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
CxxQtBuilder::new_qml_module(QmlModule::new("clide.module").qml_files(&[
|
CxxQtBuilder::new_qml_module(QmlModule::new("clide.module").qml_files(&[
|
||||||
"qml/main.qml",
|
"qml/ClideApplicationView.qml",
|
||||||
"qml/ClideAboutWindow.qml",
|
"qml/ClideEditorView.qml",
|
||||||
|
"qml/ClideExplorerView.qml",
|
||||||
"qml/ClideTreeView.qml",
|
"qml/ClideTreeView.qml",
|
||||||
"qml/ClideProjectView.qml",
|
"qml/Components/ClideAboutWindow.qml",
|
||||||
"qml/ClideEditor.qml",
|
"qml/Components/ClideBreadCrumbs.qml",
|
||||||
"qml/ClideMenuBar.qml",
|
"qml/Components/ClideEditor.qml",
|
||||||
|
"qml/Components/ClideHandle.qml",
|
||||||
|
"qml/Components/ClideLogger.qml",
|
||||||
|
"qml/Components/ClideMenu.qml",
|
||||||
|
"qml/Components/ClideMenuBar.qml",
|
||||||
|
"qml/Components/ClideMenuItem.qml",
|
||||||
|
"qml/Components/ClideScrollBar.qml",
|
||||||
|
"qml/Logger/Logger.qml",
|
||||||
|
"qml/main.qml",
|
||||||
]))
|
]))
|
||||||
// Link Qt's Network library
|
// Link Qt's Network library
|
||||||
// - Qt Core is always linked
|
// - Qt Core is always linked
|
||||||
@@ -18,6 +27,7 @@ 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();
|
||||||
}
|
}
|
||||||
|
|||||||
46
qml/ClideApplicationView.qml
Normal file
46
qml/ClideApplicationView.qml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import clide.module 1.0
|
||||||
|
import Logger 1.0
|
||||||
|
|
||||||
|
SplitView {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Path to the directory of the project opened in clide.
|
||||||
|
required property string projectDir
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// Customized handle to drag between the Navigation and the Editor.
|
||||||
|
handle: ClideHandle {
|
||||||
|
hovered: SplitHandle.hovered
|
||||||
|
pressed: SplitHandle.pressed
|
||||||
|
}
|
||||||
|
|
||||||
|
ClideExplorerView {
|
||||||
|
SplitView.fillHeight: true
|
||||||
|
SplitView.preferredWidth: 200
|
||||||
|
projectDir: root.projectDir
|
||||||
|
|
||||||
|
// Open files when clicked in the explorer.
|
||||||
|
onFileClicked: path => {
|
||||||
|
Logger.trace("Setting editor path from ClideExplorerView signal: " + path)
|
||||||
|
clideEditorView.filePath = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideEditorView {
|
||||||
|
id: clideEditorView
|
||||||
|
|
||||||
|
SplitView.fillHeight: true
|
||||||
|
SplitView.fillWidth: true
|
||||||
|
// Provide a path to the file currently open in the text editor.
|
||||||
|
// Initialized using the Default trait in Rust QML singleton FileSystem.
|
||||||
|
filePath: FileSystem.filePath
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
|
|
||||||
import clide.module 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
|
|
||||||
|
|
||||||
// Execute these behaviors when the color is changed.
|
|
||||||
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
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
topPadding: textArea.topPadding
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// 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
|
|
||||||
horizontalAlignment: Text.AlignLeft
|
|
||||||
text: parent.index + 1
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Flickable {
|
|
||||||
id: editorFlickable
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
height: 650
|
|
||||||
|
|
||||||
ScrollBar.horizontal: MyScrollBar {
|
|
||||||
}
|
|
||||||
ScrollBar.vertical: MyScrollBar {
|
|
||||||
}
|
|
||||||
|
|
||||||
TextArea.flickable: TextArea {
|
|
||||||
id: textArea
|
|
||||||
focus: true
|
|
||||||
persistentSelection: true
|
|
||||||
antialiasing: true
|
|
||||||
selectByMouse: true
|
|
||||||
selectionColor: RustColors.editor_highlight
|
|
||||||
selectedTextColor: RustColors.editor_highlighted_text
|
|
||||||
textFormat: Qt.AutoText
|
|
||||||
wrapMode: TextArea.Wrap
|
|
||||||
text: FileSystem.readFile(root.filePath)
|
|
||||||
|
|
||||||
onLinkActivated: function (link) {
|
|
||||||
Qt.openUrlExternally(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Handle saving
|
|
||||||
// Component.onCompleted: {
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
49
qml/ClideEditorView.qml
Normal file
49
qml/ClideEditorView.qml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import clide.module 1.0
|
||||||
|
import Logger 1.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// The path to the file to show in the text editor.
|
||||||
|
// This is updated by a signal caught within ClideApplicationView.
|
||||||
|
required property string filePath
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
color: "transparent"
|
||||||
|
radius: 20
|
||||||
|
|
||||||
|
SplitView {
|
||||||
|
anchors.fill: parent
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
spacing: 3
|
||||||
|
|
||||||
|
// Customized handle to drag between the Editor and the Console.
|
||||||
|
handle: ClideHandle {
|
||||||
|
hovered: SplitHandle.hovered
|
||||||
|
pressed: SplitHandle.pressed
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
SplitView.preferredHeight: 650
|
||||||
|
}
|
||||||
|
ClideLogger {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
qml/ClideExplorerView.qml
Normal file
62
qml/ClideExplorerView.qml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import clide.module 1.0
|
||||||
|
import Logger 1.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string projectDir
|
||||||
|
|
||||||
|
signal fileClicked(string path)
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
color: RustColors.explorer_background
|
||||||
|
topLeftRadius: 10
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
ClideBreadCrumbs {
|
||||||
|
id: breadCrumb
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 15
|
||||||
|
Layout.rightMargin: 15
|
||||||
|
Layout.topMargin: 10
|
||||||
|
path: clideTreeView.rootDirectory
|
||||||
|
|
||||||
|
onCrumbClicked: path => {
|
||||||
|
Logger.trace("Crumb clicked: " + path);
|
||||||
|
clideTreeView.rootDirectory = path;
|
||||||
|
}
|
||||||
|
onResetRoot: {
|
||||||
|
clideTreeView.rootDirectory = clideTreeView.originalRootDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideTreeView {
|
||||||
|
id: clideTreeView
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
// Path to the directory opened in the file explorer.
|
||||||
|
originalRootDirectory: root.projectDir
|
||||||
|
rootDirectory: root.projectDir
|
||||||
|
|
||||||
|
// Pass the signal to the parent component using another signal.
|
||||||
|
onFileClicked: path => root.fileClicked(path)
|
||||||
|
onRootDirectoryChanged: {
|
||||||
|
Logger.log("Setting root directory: " + clideTreeView.rootDirectory);
|
||||||
|
breadCrumb.path = clideTreeView.rootDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
|
|
||||||
import clide.module 1.0
|
|
||||||
|
|
||||||
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: Rectangle {
|
|
||||||
color: RustColors.menubar
|
|
||||||
border.color: RustColors.menubar_border
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// 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 {
|
|
||||||
title: qsTr("&File")
|
|
||||||
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionNewProject
|
|
||||||
}
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionOpen
|
|
||||||
onTriggered: FileSystem.setDirectory(FileSystem.filePath)
|
|
||||||
}
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionSave
|
|
||||||
}
|
|
||||||
MenuSeparator {
|
|
||||||
background: Rectangle {
|
|
||||||
border.color: color
|
|
||||||
color: RustColors.menubar_border
|
|
||||||
implicitHeight: 3
|
|
||||||
implicitWidth: 200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionExit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// 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 {
|
|
||||||
title: qsTr("&Edit")
|
|
||||||
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionUndo
|
|
||||||
}
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionRedo
|
|
||||||
}
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionCut
|
|
||||||
}
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionCopy
|
|
||||||
}
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionPaste
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// View Menu
|
|
||||||
Action {
|
|
||||||
id: actionToolWindows
|
|
||||||
|
|
||||||
text: qsTr("&Tool Windows")
|
|
||||||
}
|
|
||||||
Action {
|
|
||||||
id: actionAppearance
|
|
||||||
|
|
||||||
text: qsTr("&Appearance")
|
|
||||||
}
|
|
||||||
ClideMenu {
|
|
||||||
title: qsTr("&View")
|
|
||||||
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionToolWindows
|
|
||||||
}
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionAppearance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Help Menu
|
|
||||||
ClideAboutWindow {
|
|
||||||
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 {
|
|
||||||
title: qsTr("&Help")
|
|
||||||
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionDocumentation
|
|
||||||
}
|
|
||||||
ClideMenuItem {
|
|
||||||
action: actionAbout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
|
|
||||||
import clide.module 1.0
|
|
||||||
|
|
||||||
SplitView {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
// Path to the directory of the project opened in clide.
|
|
||||||
required property string projectDir
|
|
||||||
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
// 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
|
|
||||||
radius: 2.5
|
|
||||||
|
|
||||||
// Execute these behaviors when the color is changed.
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 400
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: navigationView
|
|
||||||
color: RustColors.explorer_background
|
|
||||||
|
|
||||||
SplitView.fillHeight: true
|
|
||||||
SplitView.minimumWidth: 0
|
|
||||||
SplitView.preferredWidth: 200
|
|
||||||
SplitView.maximumWidth: 250
|
|
||||||
|
|
||||||
StackLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
ClideTreeView {
|
|
||||||
id: clideTreeView
|
|
||||||
onFileClicked: path => root.projectDir = path
|
|
||||||
|
|
||||||
// Path to the directory opened in the file explorer.
|
|
||||||
rootDirectory: root.projectDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ClideEditor {
|
|
||||||
SplitView.fillWidth: true
|
|
||||||
|
|
||||||
// Provide a path to the file currently open in the text editor.
|
|
||||||
// Initialized using the Default trait in Rust QML singleton FileSystem.
|
|
||||||
filePath: FileSystem.filePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +1,166 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
Rectangle {
|
TreeView {
|
||||||
id: root
|
id: root
|
||||||
color: RustColors.explorer_background
|
|
||||||
|
|
||||||
required property string rootDirectory
|
property int lastIndex: -1
|
||||||
|
required property string originalRootDirectory
|
||||||
|
property string rootDirectory
|
||||||
|
property int rootIndent: 25
|
||||||
|
|
||||||
signal fileClicked(string filePath)
|
signal fileClicked(string filePath)
|
||||||
|
|
||||||
TreeView {
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
id: fileSystemTreeView
|
boundsMovement: Flickable.StopAtBounds
|
||||||
anchors.margins: 15
|
clip: true
|
||||||
|
|
||||||
property int lastIndex: -1
|
// The model is implemented in filesystem.rs
|
||||||
|
model: FileSystem
|
||||||
|
// Set the root directory on the Rust model, returning the QModeIndex to use for the root of the tree view widget.
|
||||||
|
rootIndex: FileSystem.setDirectory(root.rootDirectory)
|
||||||
|
|
||||||
model: FileSystem
|
// Provide our own custom ScrollIndicator for the TreeView.
|
||||||
anchors.fill: parent
|
ScrollBar.horizontal: ClideScrollBar {
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
sizeModifier: 3
|
||||||
boundsMovement: Flickable.StopAtBounds
|
}
|
||||||
clip: true
|
ScrollBar.vertical: ClideScrollBar {
|
||||||
|
sizeModifier: 3
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
// The delegate represents a single entry in the filesystem.
|
||||||
FileSystem.setDirectory(root.rootDirectory)
|
delegate: TreeViewDelegate {
|
||||||
fileSystemTreeView.expandRecursively(0, -1)
|
id: treeDelegate
|
||||||
|
|
||||||
|
required property string fileName
|
||||||
|
required property url filePath
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
implicitHeight: 25
|
||||||
|
implicitWidth: root.width
|
||||||
|
indentation: 12
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: current ? RustColors.explorer_folder_open : "transparent"
|
||||||
|
radius: 20
|
||||||
|
width: root.width
|
||||||
|
}
|
||||||
|
// Item name.
|
||||||
|
contentItem: Text {
|
||||||
|
anchors.left: itemIcon.right
|
||||||
|
anchors.leftMargin: 5
|
||||||
|
color: RustColors.explorer_text
|
||||||
|
text: treeDelegate.fileName
|
||||||
|
}
|
||||||
|
// Item Icon.
|
||||||
|
indicator: Label {
|
||||||
|
id: itemIcon
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
antialiasing: true
|
||||||
|
enabled: false
|
||||||
|
focus: false
|
||||||
|
font.family: localFont.font.family
|
||||||
|
font.pixelSize: 18
|
||||||
|
smooth: true
|
||||||
|
// Get the icon from Rust implementation.
|
||||||
|
text: root.model.icon(filePath)
|
||||||
|
x: root.rootIndent + (treeDelegate.depth * treeDelegate.indentation) + (carrotIndicator.visible ? carrotIndicator.width : 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The delegate represents a single entry in the filesystem.
|
// Directory carrot indicator.
|
||||||
delegate: TreeViewDelegate {
|
Label {
|
||||||
id: treeDelegate
|
id: carrotIndicator
|
||||||
indentation: 8
|
|
||||||
implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250
|
|
||||||
implicitHeight: 25
|
|
||||||
|
|
||||||
required property int index
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
required property url filePath
|
font.family: localFont.font.family
|
||||||
required property string fileName
|
font.pixelSize: 10
|
||||||
|
font.weight: localFont.font.weight
|
||||||
|
text: expanded ? "⮟" : "⮞"
|
||||||
|
visible: isTreeNode && hasChildren
|
||||||
|
x: (root.rootIndent - implicitWidth) + (depth * indentation)
|
||||||
|
}
|
||||||
|
// Apply colorization effects to the icon for the item.
|
||||||
|
MultiEffect {
|
||||||
|
anchors.fill: itemIcon
|
||||||
|
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: itemIcon
|
||||||
|
}
|
||||||
|
HoverHandler {
|
||||||
|
id: hoverHandler
|
||||||
|
|
||||||
indicator: Image {
|
acceptedDevices: PointerDevice.Mouse
|
||||||
id: directoryIcon
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
|
||||||
function setSourceImage() {
|
onSingleTapped: (eventPoint, button) => {
|
||||||
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>";
|
switch (button) {
|
||||||
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>";
|
case Qt.LeftButton:
|
||||||
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) {
|
if (treeDelegate.hasChildren) {
|
||||||
return treeDelegate.expanded ?
|
root.toggleExpanded(treeDelegate.row);
|
||||||
folderOpen : folderClosed;
|
|
||||||
} else {
|
} else {
|
||||||
return file
|
// If this model item doesn't have children, it means it's representing a file.
|
||||||
}
|
root.fileClicked(treeDelegate.filePath);
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: {
|
|
||||||
console.log("Setting directory: " + treeDelegate.filePath)
|
|
||||||
FileSystem.setDirectory(treeDelegate.filePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action {
|
|
||||||
text: qsTr("Reset root index")
|
|
||||||
onTriggered: {
|
|
||||||
FileSystem.setDirectory("")
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case Qt.RightButton:
|
||||||
|
contextMenu.popup();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ClideMenu {
|
||||||
|
id: contextMenu
|
||||||
|
|
||||||
// Provide our own custom ScrollIndicator for the TreeView.
|
ClideMenuItem {
|
||||||
ScrollIndicator.vertical: ScrollIndicator {
|
action: Action {
|
||||||
active: true
|
enabled: treeDelegate.hasChildren
|
||||||
implicitWidth: 15
|
text: qsTr("Set root")
|
||||||
|
|
||||||
contentItem: Rectangle {
|
onTriggered: {
|
||||||
implicitWidth: 6
|
Logger.debug("Setting new root directory: " + treeDelegate.filePath);
|
||||||
implicitHeight: 6
|
root.rootDirectory = treeDelegate.filePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
text: qsTr("Reset root")
|
||||||
|
|
||||||
color: RustColors.scrollbar
|
onTriggered: {
|
||||||
opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
|
Logger.log("Resetting root directory: " + root.originalRootDirectory);
|
||||||
|
root.rootDirectory = root.originalRootDirectory;
|
||||||
Behavior on opacity {
|
|
||||||
OpacityAnimator {
|
|
||||||
duration: 500
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
selectionModel: ItemSelectionModel {
|
||||||
|
}
|
||||||
|
|
||||||
|
FontLoader {
|
||||||
|
id: localFont
|
||||||
|
|
||||||
|
source: "qrc:/fonts/saucecodepro-xlight.ttf"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
// TODO: Header
|
// 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.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
|
||||||
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
|
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.
|
// Hide the window when it loses focus.
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (!active) {
|
Logger.debug("Setting active: " + root.active)
|
||||||
|
if (!root.active) {
|
||||||
root.visible = false;
|
root.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,48 +31,38 @@ ApplicationWindow {
|
|||||||
id: logo
|
id: logo
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
anchors.margins: 20
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.margins: 20
|
|
||||||
|
|
||||||
source: "../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.top: logo.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
anchors.margins: 20
|
anchors.margins: 20
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: logo.bottom
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
selectedTextColor: RustColors.editor_highlighted_text
|
|
||||||
selectionColor: RustColors.editor_highlight
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
textFormat: Text.RichText
|
|
||||||
|
|
||||||
text: qsTr("<h3>About CLIDE</h3>"
|
|
||||||
+ "<p>A simple text editor written in Rust and QML using CXX-Qt.</p>"
|
|
||||||
+ "<p>Personal website <a href=\"http://shaunreed.com\">shaunreed.com</a></p>"
|
|
||||||
+ "<p>Project notes <a href=\"http://knoats.com\">knoats.com</a></p>"
|
|
||||||
+ "<p>This project is developed at <a href=\"http://git.shaunreed.com/shaunrd0/clide\">git.shaunreed.com</a></p>"
|
|
||||||
+ "<p><a href=\"https://github.com/KDAB/cxx-qt\">KDAB CXX-Qt repository</a></p>"
|
|
||||||
+ "<p>Copyright (C) 2026 Shaun Reed, all rights reserved.</p>")
|
|
||||||
color: RustColors.editor_text
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
readOnly: true
|
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
background: null
|
background: null
|
||||||
|
color: RustColors.editor_text
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
readOnly: true
|
||||||
|
selectedTextColor: RustColors.editor_highlighted_text
|
||||||
|
selectionColor: RustColors.editor_highlight
|
||||||
|
text: qsTr("<h3>About CLIDE</h3>" + "<p>A simple text editor written in Rust and QML using CXX-Qt.</p>" + "<p>Personal website <a href=\"http://shaunreed.com\">shaunreed.com</a></p>" + "<p>Project notes <a href=\"http://knoats.com\">knoats.com</a></p>" + "<p>This project is developed at <a href=\"http://git.shaunreed.com/shaunrd0/clide\">git.shaunreed.com</a></p>" + "<p><a href=\"https://github.com/KDAB/cxx-qt\">KDAB CXX-Qt repository</a></p>" + "<p>Copyright (C) 2026 Shaun Reed, all rights reserved.</p>")
|
||||||
|
textFormat: Text.RichText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
onLinkActivated: function (link) {
|
onLinkActivated: function (link) {
|
||||||
Qt.openUrlExternally(link)
|
Qt.openUrlExternally(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
107
qml/Components/ClideBreadCrumbs.qml
Normal file
107
qml/Components/ClideBreadCrumbs.qml
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import clide.module 1.0
|
||||||
|
import Logger 1.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var fullPaths: []
|
||||||
|
required property string path
|
||||||
|
property var segments: []
|
||||||
|
|
||||||
|
signal crumbClicked(string path)
|
||||||
|
signal resetRoot
|
||||||
|
|
||||||
|
function rebuildSegments(): string {
|
||||||
|
let cleaned = path;
|
||||||
|
if (cleaned.endsWith("/"))
|
||||||
|
cleaned = cleaned.slice(0, -1);
|
||||||
|
Logger.trace("Building segments for path: " + cleaned);
|
||||||
|
segments = ["/"];
|
||||||
|
fullPaths = ["/"];
|
||||||
|
let parts = cleaned.split("/");
|
||||||
|
let current = "";
|
||||||
|
// We already pushed the root `/` path during initialization, so skip index 0.
|
||||||
|
for (let i = 1; i < parts.length; ++i) {
|
||||||
|
current += "/" + parts[i];
|
||||||
|
Logger.trace("Pushing path: " + parts[i] + " Current: " + current);
|
||||||
|
segments.push(parts[i]);
|
||||||
|
fullPaths.push(current);
|
||||||
|
}
|
||||||
|
// Update the model used in the Repeater to show the new segments.
|
||||||
|
repeater.model = segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
implicitHeight: breadcrumbRow.implicitHeight
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
Component.onCompleted: rebuildSegments()
|
||||||
|
onPathChanged: rebuildSegments()
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
id: breadcrumbRow
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 2
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: repeater
|
||||||
|
|
||||||
|
model: root.segments
|
||||||
|
|
||||||
|
delegate: Text {
|
||||||
|
required property int index
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
function getText(): string {
|
||||||
|
if (modelData === "/") {
|
||||||
|
return modelData;
|
||||||
|
}
|
||||||
|
return modelData + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show blue underlined hyperlink text if the mouse is hovering a segment.
|
||||||
|
color: mouseArea.containsMouse ? "#2a7fff" : RustColors.explorer_text
|
||||||
|
font.underline: mouseArea.containsMouse
|
||||||
|
text: getText()
|
||||||
|
|
||||||
|
// Click events for each path segment call signal so the parent can set the file explorer root path.
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
Logger.info(index + "] Breadcrumb clicked:" + root.fullPaths[index]);
|
||||||
|
crumbClicked(root.fullPaths[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
|
||||||
|
onSingleTapped: contextMenu.popup()
|
||||||
|
}
|
||||||
|
ClideMenu {
|
||||||
|
id: contextMenu
|
||||||
|
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
text: qsTr("Reset root")
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
Logger.info("Resetting root directory from ClideBreadCrumbs");
|
||||||
|
resetRoot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
qml/Components/ClideEditor.qml
Normal file
116
qml/Components/ClideEditor.qml
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GNU General Public License v3.0 or later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import clide.module 1.0
|
||||||
|
import Logger 1.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: RustColors.editor_background
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// We use a flickable to synchronize the position of the editor and
|
||||||
|
// the line numbers. This is necessary because the line numbers can
|
||||||
|
// extend the available height.
|
||||||
|
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
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
topPadding: textArea.topPadding
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// Show the line number.
|
||||||
|
Label {
|
||||||
|
id: numbers
|
||||||
|
|
||||||
|
color: RustColors.linenumber
|
||||||
|
font: textArea.font
|
||||||
|
height: parent.height
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
text: parent.index + 1
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
width: parent.width - indicator.width
|
||||||
|
}
|
||||||
|
// Draw an 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 {
|
||||||
|
id: editorFlickable
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FontMetrics {
|
||||||
|
id: fontMetrics
|
||||||
|
|
||||||
|
font: textArea.font
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
qml/Components/ClideHandle.qml
Normal file
44
qml/Components/ClideHandle.qml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// 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 "transparent";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
required property bool hovered
|
||||||
|
required property bool pressed
|
||||||
|
|
||||||
|
border.color: currentColor
|
||||||
|
color: currentColor
|
||||||
|
implicitHeight: 8
|
||||||
|
implicitWidth: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
qml/Components/ClideLogger.qml
Normal file
60
qml/Components/ClideLogger.qml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: RustColors.terminal_background
|
||||||
|
radius: 10
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: model
|
||||||
|
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
14
qml/Components/ClideMenu.qml
Normal file
14
qml/Components/ClideMenu.qml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
153
qml/Components/ClideMenuBar.qml
Normal file
153
qml/Components/ClideMenuBar.qml
Normal file
@@ -0,0 +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.Controls
|
||||||
|
|
||||||
|
import clide.module 1.0
|
||||||
|
|
||||||
|
MenuBar {
|
||||||
|
// Background for this MenuBar.
|
||||||
|
background: Rectangle {
|
||||||
|
color: RustColors.menubar
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// File Menu
|
||||||
|
ClideMenu {
|
||||||
|
title: qsTr("&File")
|
||||||
|
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionNewProject
|
||||||
|
|
||||||
|
text: qsTr("&New Project...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionOpen
|
||||||
|
|
||||||
|
text: qsTr("&Open...")
|
||||||
|
}
|
||||||
|
|
||||||
|
onTriggered: FileSystem.setDirectory(FileSystem.filePath)
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionSave
|
||||||
|
|
||||||
|
text: qsTr("&Save")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuSeparator {
|
||||||
|
background: Rectangle {
|
||||||
|
border.color: color
|
||||||
|
color: Qt.darker(RustColors.menubar, 1)
|
||||||
|
implicitHeight: 3
|
||||||
|
implicitWidth: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionExit
|
||||||
|
|
||||||
|
text: qsTr("&Exit")
|
||||||
|
|
||||||
|
onTriggered: Qt.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Edit Menu
|
||||||
|
ClideMenu {
|
||||||
|
title: qsTr("&Edit")
|
||||||
|
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionUndo
|
||||||
|
|
||||||
|
text: qsTr("&Undo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionRedo
|
||||||
|
|
||||||
|
text: qsTr("&Redo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionCut
|
||||||
|
|
||||||
|
text: qsTr("&Cut")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionCopy
|
||||||
|
|
||||||
|
text: qsTr("&Copy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionPaste
|
||||||
|
|
||||||
|
text: qsTr("&Paste")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// View Menu
|
||||||
|
ClideMenu {
|
||||||
|
title: qsTr("&View")
|
||||||
|
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionAppearance
|
||||||
|
|
||||||
|
text: qsTr("&Appearance")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionToolWindows
|
||||||
|
|
||||||
|
text: qsTr("&Tool Windows")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Help Menu
|
||||||
|
ClideAboutWindow {
|
||||||
|
id: clideAbout
|
||||||
|
|
||||||
|
}
|
||||||
|
ClideMenu {
|
||||||
|
title: qsTr("&Help")
|
||||||
|
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionDocumentation
|
||||||
|
|
||||||
|
text: qsTr("&Documentation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClideMenuItem {
|
||||||
|
action: Action {
|
||||||
|
id: actionAbout
|
||||||
|
|
||||||
|
text: qsTr("&About")
|
||||||
|
|
||||||
|
// Toggle the about window with the menu item is clicked.
|
||||||
|
onTriggered: clideAbout.visible = !clideAbout.visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
qml/Components/ClideMenuItem.qml
Normal file
18
qml/Components/ClideMenuItem.qml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
74
qml/Components/ClideScrollBar.qml
Normal file
74
qml/Components/ClideScrollBar.qml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// 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 ? sizeModifier * 2 : sizeModifier
|
||||||
|
property int o: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
|
||||||
|
property int sizeModifier: 4
|
||||||
|
property int w: scrollBar.interactive ? sizeModifier * 2 : sizeModifier
|
||||||
|
|
||||||
|
// 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
|
||||||
|
radius: 20
|
||||||
|
|
||||||
|
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
|
||||||
|
radius: 20
|
||||||
|
|
||||||
|
// Smooth transition between color changes based on the state above.
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on opacity {
|
||||||
|
OpacityAnimator {
|
||||||
|
duration: 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
qml/Components/qmldir
Normal file
9
qml/Components/qmldir
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
ClideScrollBar ClideScrollBar.qml
|
||||||
|
ClideHandle ClideHandle.qml
|
||||||
|
ClideMenu ClideMenu.qml
|
||||||
|
ClideMenuItem ClideMenuItem.qml
|
||||||
|
ClideBreadCrumbs ClideBreadCrumbs.qml
|
||||||
|
ClideAboutWindow ClideAboutWindow.qml
|
||||||
|
ClideEditor ClideEditor.qml
|
||||||
|
ClideLogger ClideLogger.qml
|
||||||
|
ClideMenuBar ClideMenuBar.qml
|
||||||
35
qml/Logger/Logger.qml
Normal file
35
qml/Logger/Logger.qml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// 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
qml/Logger/qmldir
Normal file
1
qml/Logger/qmldir
Normal file
@@ -0,0 +1 @@
|
|||||||
|
singleton Logger 1.0 Logger.qml
|
||||||
32
qml/main.qml
32
qml/main.qml
@@ -1,3 +1,7 @@
|
|||||||
|
// 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
|
||||||
@@ -8,28 +12,32 @@ 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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
color: RustColors.menubar
|
||||||
color: RustColors.gutter
|
width: appView.implicitWidth
|
||||||
}
|
height: appView.implicitHeight
|
||||||
|
|
||||||
MessageDialog {
|
ClideApplicationView {
|
||||||
id: errorDialog
|
id: appView
|
||||||
|
projectDir: appWindow.appContextPath
|
||||||
|
implicitHeight: appWindow.height
|
||||||
|
implicitWidth: appWindow.width
|
||||||
|
|
||||||
title: qsTr("Error")
|
anchors.right: parent.right
|
||||||
}
|
anchors.bottom: parent.bottom
|
||||||
ClideProjectView {
|
anchors.leftMargin: 20
|
||||||
projectDir: appWindow.appContextPath
|
anchors.topMargin: 10
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
resources.qrc
Normal file
10
resources.qrc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<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>
|
||||||
BIN
resources/SauceCodeProNerdFont-Black.ttf
Normal file
BIN
resources/SauceCodeProNerdFont-Black.ttf
Normal file
Binary file not shown.
BIN
resources/SauceCodeProNerdFont-ExtraLight.ttf
Normal file
BIN
resources/SauceCodeProNerdFont-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
resources/SauceCodeProNerdFont-Light.ttf
Normal file
BIN
resources/SauceCodeProNerdFont-Light.ttf
Normal file
Binary file not shown.
BIN
resources/gui.png
Normal file
BIN
resources/gui.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 402 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@@ -1,3 +1,7 @@
|
|||||||
|
// 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,4 +1,9 @@
|
|||||||
|
// 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");
|
||||||
@@ -31,6 +36,12 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,6 +71,12 @@ 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 {
|
||||||
@@ -68,7 +85,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("#3c3f41").unwrap(),
|
menubar: QColor::try_from("#262626").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(),
|
||||||
@@ -76,17 +93,23 @@ 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("#2b2b2b").unwrap(),
|
editor_background: QColor::try_from("#1E1F22").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("#3b3b3b").unwrap(),
|
explorer_text: QColor::try_from("#FFF").unwrap(),
|
||||||
explorer_text_selected: QColor::try_from("#8b8b8b").unwrap(),
|
explorer_text_selected: QColor::try_from("#262626").unwrap(),
|
||||||
explorer_background: QColor::try_from("#676c70").unwrap(),
|
explorer_background: QColor::try_from("#1E1F22").unwrap(),
|
||||||
explorer_folder: QColor::try_from("#54585b").unwrap(),
|
explorer_folder: QColor::try_from("#54585b").unwrap(),
|
||||||
explorer_folder_open: QColor::try_from("#FFF").unwrap(),
|
explorer_folder_open: QColor::try_from("#393B40").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,3 +1,19 @@
|
|||||||
|
// 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};
|
||||||
|
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++" {
|
||||||
@@ -17,7 +33,6 @@ 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]
|
||||||
@@ -36,25 +51,14 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
||||||
@@ -62,7 +66,6 @@ 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(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,42 +75,47 @@ impl qobject::FileSystem {
|
|||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
return QString::default();
|
return QString::default();
|
||||||
}
|
}
|
||||||
if !fs::metadata(path.to_string())
|
let meta = 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());
|
||||||
.is_file()
|
if !meta.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 ss = SyntaxSet::load_defaults_nonewlines();
|
let path_str = path.to_string();
|
||||||
let ts = ThemeSet::load_defaults();
|
if let Ok(lines) = fs::read_to_string(path_str.as_str()) {
|
||||||
let theme = &ts.themes["base16-ocean.dark"];
|
let ss = SyntaxSet::load_defaults_nonewlines();
|
||||||
|
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);
|
||||||
|
// If you care about the background, see `start_highlighted_html_snippet(theme);`.
|
||||||
|
let mut output = String::from("<pre>\n");
|
||||||
|
for line in LinesWithEndings::from(lines.as_str()) {
|
||||||
|
let regions = highlighter
|
||||||
|
.highlight_line(line, &ss)
|
||||||
|
.expect("Failed to highlight");
|
||||||
|
|
||||||
let mut highlighter =
|
append_highlighted_html_for_styled_line(
|
||||||
HighlightFile::new(path.to_string(), &ss, theme).expect("Failed to create highlighter");
|
®ions[..],
|
||||||
let (mut output, _bg) = start_highlighted_html_snippet(theme);
|
IncludeBackground::No,
|
||||||
let mut line = String::new();
|
&mut output,
|
||||||
while highlighter
|
)
|
||||||
.reader
|
.expect("Failed to insert highlighted html");
|
||||||
.read_line(&mut line)
|
}
|
||||||
.expect("Failed to read file.")
|
|
||||||
> 0
|
|
||||||
{
|
|
||||||
let regions = highlighter
|
|
||||||
.highlight_lines
|
|
||||||
.highlight_line(&line, &ss)
|
|
||||||
.expect("Failed to highlight");
|
|
||||||
|
|
||||||
append_highlighted_html_for_styled_line(
|
output.push_str("</pre>\n");
|
||||||
®ions[..],
|
QString::from(output)
|
||||||
IncludeBackground::Yes,
|
} else {
|
||||||
&mut output,
|
return QString::default();
|
||||||
)
|
|
||||||
.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.
|
||||||
@@ -124,14 +132,24 @@ 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.
|
||||||
self.set_root_path(&QString::from(
|
let homedir = dirs::home_dir()
|
||||||
dirs::home_dir()
|
.expect("Failed to get home directory")
|
||||||
.expect("Failed to get home directory")
|
.as_path()
|
||||||
.as_path()
|
.to_str()
|
||||||
.to_str()
|
.unwrap()
|
||||||
.unwrap()
|
.to_string();
|
||||||
.to_string(),
|
self.set_root_path(&QString::from(homedir))
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,3 +1,7 @@
|
|||||||
|
// 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,3 +1,7 @@
|
|||||||
|
// 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,3 +1,7 @@
|
|||||||
|
// 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,3 +1,7 @@
|
|||||||
|
// 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,3 +1,7 @@
|
|||||||
|
// 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,3 +1,7 @@
|
|||||||
|
// 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,3 +1,7 @@
|
|||||||
|
// 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,3 +1,7 @@
|
|||||||
|
// 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,3 +1,7 @@
|
|||||||
|
// 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,3 +1,7 @@
|
|||||||
|
// 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