1 Commits

Author SHA1 Message Date
1b60947177 WIP 2026-02-07 21:30:26 -05:00
20 changed files with 362 additions and 373 deletions

View File

@@ -107,8 +107,6 @@ cargo run
clide clide
``` ```
![image](./resources/gui.png)
## Development ## Development
It's recommended to use RustRover or Qt Creator for development. It's recommended to use RustRover or Qt Creator for development.

View File

@@ -2,21 +2,20 @@ 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/ClideApplicationView.qml",
"qml/ClideEditorView.qml",
"qml/ClideExplorerView.qml",
"qml/ClideTreeView.qml",
"qml/Components/ClideAboutWindow.qml",
"qml/Components/ClideBreadCrumbs.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", "qml/main.qml",
"qml/ClideAboutWindow.qml",
"qml/ClideTreeView.qml",
"qml/ClideProjectView.qml",
"qml/ClideEditor.qml",
"qml/ClideEditorView.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

View File

@@ -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
// 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
}
}

136
qml/ClideEditor.qml Normal file
View File

@@ -0,0 +1,136 @@
// 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
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 {
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
background: Rectangle {
color: RustColors.terminal_background
}
}
// 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: 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
// 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
}
}
}

View File

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

View File

@@ -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 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;
}
}
}
}

View File

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

91
qml/ClideProjectView.qml Normal file
View File

@@ -0,0 +1,91 @@
// 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
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
SplitView.fillHeight: true
SplitView.maximumWidth: 250
SplitView.minimumWidth: 0
SplitView.preferredWidth: 200
color: RustColors.explorer_background
ColumnLayout {
spacing: 2
ClideBreadCrumbs {
id: breadCrumb
Layout.bottomMargin: 5
Layout.leftMargin: 15
Layout.topMargin: 5
path: clideTreeView.rootDirectory
onCrumbClicked: path => {
Logger.trace("Crumb clicked: " + path);
clideTreeView.rootDirectory = path;
}
}
ClideTreeView {
id: clideTreeView
height: navigationView.height
// Path to the directory opened in the file explorer.
originalRootDirectory: root.projectDir
rootDirectory: root.projectDir
width: navigationView.width
onFileClicked: path => clideEditor.filePath = path
onRootDirectoryChanged: {
Logger.log("Setting root directory: " + clideTreeView.rootDirectory);
breadCrumb.path = clideTreeView.rootDirectory;
}
}
}
}
ClideEditorView {
id: clideEditor
SplitView.fillWidth: true
// Provide a path to the file currently open in the text editor.
// Initialized using the Default trait in Rust QML singleton FileSystem.
filePath: FileSystem.filePath
}
}

View File

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

View File

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

View File

@@ -1,116 +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
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
}
}
}
}

View File

@@ -16,7 +16,7 @@ Rectangle {
} else if (hovered) { } else if (hovered) {
return RustColors.hovered; return RustColors.hovered;
} else { } else {
return "transparent"; return RustColors.gutter;
} }
} }
required property bool hovered required property bool hovered
@@ -25,7 +25,6 @@ Rectangle {
border.color: currentColor border.color: currentColor
color: currentColor color: currentColor
implicitHeight: 8 implicitHeight: 8
implicitWidth: 8
radius: 2.5 radius: 2.5
opacity: root.hovered ? 1.0 : 0.0 opacity: root.hovered ? 1.0 : 0.0

View File

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

View File

@@ -3,7 +3,3 @@ ClideHandle ClideHandle.qml
ClideMenu ClideMenu.qml ClideMenu ClideMenu.qml
ClideMenuItem ClideMenuItem.qml ClideMenuItem ClideMenuItem.qml
ClideBreadCrumbs ClideBreadCrumbs.qml ClideBreadCrumbs ClideBreadCrumbs.qml
ClideAboutWindow ClideAboutWindow.qml
ClideEditor ClideEditor.qml
ClideLogger ClideLogger.qml
ClideMenuBar ClideMenuBar.qml

View File

@@ -15,29 +15,22 @@ ApplicationWindow {
required property string appContextPath required property string appContextPath
height: 800 height: 800
title: "Clide" title: "CLIDE"
visible: true visible: true
width: 1200 width: 1200
menuBar: ClideMenuBar { menuBar: ClideMenuBar {
} }
Rectangle { MessageDialog {
color: RustColors.menubar id: errorDialog
width: appView.implicitWidth
height: appView.implicitHeight
ClideApplicationView { title: qsTr("Error")
id: appView }
projectDir: appWindow.appContextPath ClideProjectView {
implicitHeight: appWindow.height id: clideProjectView
implicitWidth: appWindow.width
anchors.right: parent.right projectDir: appWindow.appContextPath
anchors.bottom: parent.bottom
anchors.leftMargin: 20
anchors.topMargin: 10
}
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

View File

@@ -93,7 +93,7 @@ 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("#1E1F22").unwrap(), editor_background: QColor::try_from("#111111").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(),

View File

@@ -10,7 +10,9 @@ use std::fs;
use std::path::Path; use std::path::Path;
use syntect::easy::HighlightLines; use syntect::easy::HighlightLines;
use syntect::highlighting::ThemeSet; use syntect::highlighting::ThemeSet;
use syntect::html::{IncludeBackground, append_highlighted_html_for_styled_line}; use syntect::html::{
IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet,
};
use syntect::parsing::SyntaxSet; use syntect::parsing::SyntaxSet;
use syntect::util::LinesWithEndings; use syntect::util::LinesWithEndings;
@@ -57,6 +59,7 @@ pub mod qobject {
} }
} }
// TODO: Implement a provider for QFileSystemModel::setIconProvider for icons.
pub struct FileSystemImpl { pub struct FileSystemImpl {
file_path: QString, file_path: QString,
} }
@@ -96,8 +99,7 @@ impl qobject::FileSystem {
) )
.unwrap_or_else(|| ss.find_syntax_plain_text()); .unwrap_or_else(|| ss.find_syntax_plain_text());
let mut highlighter = HighlightLines::new(lang, theme); let mut highlighter = HighlightLines::new(lang, theme);
// If you care about the background, see `start_highlighted_html_snippet(theme);`. let (mut output, _bg) = start_highlighted_html_snippet(theme);
let mut output = String::from("<pre>\n");
for line in LinesWithEndings::from(lines.as_str()) { for line in LinesWithEndings::from(lines.as_str()) {
let regions = highlighter let regions = highlighter
.highlight_line(line, &ss) .highlight_line(line, &ss)