Add LineCount module.

This commit is contained in:
Shaun Reed 2025-03-29 16:55:26 -04:00
parent a6d2fb9e31
commit 13a405a801
7 changed files with 386 additions and 98 deletions

7
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target **/target/**
.qtcreator **/.qtcreator/**
.idea **/.idea/**
**/*.autosave/**

7
Cargo.lock generated
View File

@ -62,6 +62,7 @@ dependencies = [
"cxx-qt", "cxx-qt",
"cxx-qt-build", "cxx-qt-build",
"cxx-qt-lib", "cxx-qt-lib",
"log",
] ]
[[package]] [[package]]
@ -266,6 +267,12 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"

View File

@ -7,6 +7,7 @@ edition = "2024"
cxx = "1.0.95" cxx = "1.0.95"
cxx-qt = "0.7" cxx-qt = "0.7"
cxx-qt-lib = { version="0.7", features = ["qt_full"] } cxx-qt-lib = { version="0.7", features = ["qt_full"] }
log = { version = "0.4.27", features = [] }
[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.

View File

@ -9,7 +9,7 @@ fn main() {
// - Qt Qml requires linking Qt Network on macOS // - Qt Qml requires linking Qt Network on macOS
.qt_module("Network") .qt_module("Network")
.qml_module(QmlModule { .qml_module(QmlModule {
uri: "test", uri: "clide.module",
rust_files: &["src/main.rs"], rust_files: &["src/main.rs"],
qml_files: &["qml/main.qml", qml_files: &["qml/main.qml",
"qml/Menu/ClideMenu.qml", "qml/Menu/ClideMenu.qml",

View File

@ -0,0 +1,140 @@
import QtQuick
import QtQuick.Controls
MenuBar {
background: Rectangle {
color: "#3c3f41"
border.color: "#575757"
}
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")
ClideMenuBarItem {
action: actionNewProject
}
ClideMenuBarItem {
action: actionOpen
}
ClideMenuBarItem {
action: actionSave
}
MenuSeparator {
background: Rectangle {
border.color: color
color: "#3c3f41"
implicitHeight: 3
implicitWidth: 200
}
}
ClideMenuBarItem {
action: actionExit
}
}
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")
ClideMenuBarItem {
action: actionUndo
}
ClideMenuBarItem {
action: actionRedo
}
ClideMenuBarItem {
action: actionCut
}
ClideMenuBarItem {
action: actionCopy
}
ClideMenuBarItem {
action: actionPaste
}
}
Action {
id: actionToolWindows
text: qsTr("&Tool Windows")
}
Action {
id: actionAppearance
text: qsTr("&Appearance")
}
ClideMenu {
title: qsTr("&View")
ClideMenuBarItem {
action: actionToolWindows
}
ClideMenuBarItem {
action: actionAppearance
}
}
Action {
id: actionDocumentation
text: qsTr("&Documentation")
}
Action {
id: actionAbout
text: qsTr("&About")
}
ClideMenu {
title: qsTr("&Help")
ClideMenuBarItem {
action: actionDocumentation
}
ClideMenuBarItem {
action: actionAbout
}
}
}

View File

@ -5,6 +5,8 @@ import QtQuick.Dialogs
import "Menu" import "Menu"
import clide.module 1.0
ApplicationWindow { ApplicationWindow {
id: appWindow id: appWindow
@ -51,7 +53,7 @@ ApplicationWindow {
id: navigationView id: navigationView
SplitView.fillHeight: true SplitView.fillHeight: true
SplitView.preferredWidth: 250 SplitView.preferredWidth: 200
color: "#303234" color: "#303234"
StackLayout { StackLayout {
@ -59,9 +61,9 @@ ApplicationWindow {
// Shows the help text. // Shows the help text.
TextArea { TextArea {
readOnly: true
placeholderText: qsTr("File system view placeholder") placeholderText: qsTr("File system view placeholder")
placeholderTextColor: "white" placeholderTextColor: "white"
readOnly: true
wrapMode: TextArea.Wrap wrapMode: TextArea.Wrap
} }
@ -92,7 +94,79 @@ ApplicationWindow {
} }
} }
TextArea { 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
// Calculate the width based on the logarithmic scale.
Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(areaText.lineCount)) + 1) + 10
contentY: editorFlickable.contentY
interactive: false
visible: true
Column {
anchors.fill: parent
Repeater {
id: repeatedLineNumbers
delegate: Item {
required property int index
height: Math.ceil(fontMetrics.lineSpacing)
width: parent.width
Label {
id: numbers
color: "white"
font: areaText.font
height: parent.height
horizontalAlignment: Text.AlignLeft
text: parent.index + 1
verticalAlignment: Text.AlignVCenter
width: parent.width
}
Rectangle {
id: indicator
anchors.left: numbers.right
color: Qt.darker("#FFF", 3)
height: parent.height
width: 1
}
}
model: LineCount {
id: lineCountModel
// This count sets the max line numbers shown in the gutter.
count: areaText.lineCount
}
}
}
}
Flickable {
id: editorFlickable
height: 650
property alias areaText: areaText
Layout.fillHeight: true
Layout.fillWidth: true
boundsBehavior: Flickable.StopAtBounds
ScrollBar.horizontal: MyScrollBar {
}
ScrollBar.vertical: MyScrollBar {
}
TextArea.flickable: TextArea {
id: areaText id: areaText
color: "#ccced3" color: "#ccced3"
@ -106,7 +180,6 @@ ApplicationWindow {
background: Rectangle { background: Rectangle {
color: "#2b2b2b" color: "#2b2b2b"
implicitHeight: 650
} }
onLinkActivated: function (link) { onLinkActivated: function (link) {
@ -135,12 +208,20 @@ ApplicationWindow {
// } // }
// } // }
} }
FontMetrics {
id: fontMetrics
font: areaText.font
}
}
}
TextArea { TextArea {
id: areaConsole id: areaConsole
height: 100
placeholderText: qsTr("Placeholder for bash terminal.") placeholderText: qsTr("Placeholder for bash terminal.")
placeholderTextColor: "white" placeholderTextColor: "white"
height: 100
readOnly: true readOnly: true
wrapMode: TextArea.Wrap wrapMode: TextArea.Wrap
@ -153,4 +234,36 @@ ApplicationWindow {
} }
} }
} }
// 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
background: Rectangle {
color: "#2b2b2b"
implicitHeight: scrollBar.interactive ? 8 : 4
implicitWidth: scrollBar.interactive ? 8 : 4
opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
Behavior on opacity {
OpacityAnimator {
duration: 500
}
}
}
contentItem: Rectangle {
color: "#4b4f51"
implicitHeight: scrollBar.interactive ? 8 : 4
implicitWidth: scrollBar.interactive ? 8 : 4
opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
Behavior on opacity {
OpacityAnimator {
duration: 1000
}
}
}
}
} }

View File

@ -1,77 +1,103 @@
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> // TODO: Header
// SPDX-FileContributor: Leon Matthes <leon.matthes@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#[cxx_qt::bridge] #[cxx_qt::bridge]
mod qobject { pub mod qobject {
unsafe extern "C++" { unsafe extern "C++" {
include!("cxx-qt-lib/qstring.h"); include!("cxx-qt-lib/qvariant.h");
type QString = cxx_qt_lib::QString; type QVariant = cxx_qt_lib::QVariant;
} include!(<QtCore/QAbstractListModel>);
type QModelIndex = cxx_qt_lib::QModelIndex;
#[qenum(Greeter)] type QAbstractListModel;
pub enum Language {
English,
German,
French,
}
#[qenum(Greeter)]
pub enum Greeting {
Hello,
Bye,
} }
unsafe extern "RustQt" { unsafe extern "RustQt" {
#[qobject] #[qobject]
#[base = QAbstractListModel]
type AbstractListModel = super::AbstractListModelRust;
#[qobject]
#[base = AbstractListModel]
#[qml_element] #[qml_element]
#[qproperty(Greeting, greeting)] #[qproperty(i32, count)]
#[qproperty(Language, language)] type LineCount = super::LineCountRust;
type Greeter = super::GreeterRust;
#[cxx_name = "beginInsertRows"]
#[inherit]
fn beginInsertRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32);
#[cxx_name = "endInsertRows"]
#[inherit]
fn endInsertRows(self: Pin<&mut LineCount>);
#[cxx_name = "beginRemoveRows"]
#[inherit]
fn beginRemoveRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32);
#[cxx_name = "endRemoveRows"]
#[inherit]
fn endRemoveRows(self: Pin<&mut LineCount>);
#[qinvokable] #[qinvokable]
fn greet(self: &Greeter) -> QString; pub fn set_line_count(self: Pin<&mut LineCount>, line_count: i32);
#[qinvokable]
#[cxx_override]
fn data(self: &LineCount, index: &QModelIndex, role: i32) -> QVariant;
#[qinvokable]
#[cxx_override]
#[cxx_name = "rowCount"]
fn row_count(self: &LineCount, _parent: &QModelIndex) -> i32;
} }
} }
use qobject::*; use cxx_qt::CxxQtType;
use cxx_qt_lib::{QModelIndex, QVariant};
impl Greeting { impl qobject::LineCount {
fn translate(&self, language: Language) -> String { pub fn set_line_count(mut self: std::pin::Pin<&mut Self>, line_count: i32) {
match (self, language) { let current_count = self.as_mut().rust_mut().count;
(&Greeting::Hello, Language::English) => "Hello, World!", if line_count < 0 || current_count == line_count {
(&Greeting::Hello, Language::German) => "Hallo, Welt!", log::warn!(
(&Greeting::Hello, Language::French) => "Bonjour, le monde!", "Can't set line count: {}; Current count: {}",
(&Greeting::Bye, Language::English) => "Bye!", line_count,
(&Greeting::Bye, Language::German) => "Auf Wiedersehen!", current_count
(&Greeting::Bye, Language::French) => "Au revoir!", );
_ => "🤯", return;
} }
.to_string() if current_count < line_count {
self.as_mut()
.beginInsertRows(&QModelIndex::default(), current_count, line_count - 1);
self.as_mut().endInsertRows();
} else if current_count > line_count {
self.as_mut()
.beginRemoveRows(&QModelIndex::default(), line_count, current_count - 1);
self.as_mut().endRemoveRows();
}
self.as_mut().rust_mut().count = line_count;
log::warn!(
"Line count changed from {} to {}",
current_count,
line_count
);
}
pub fn row_count(self: &Self, _parent: &QModelIndex) -> i32 {
*self.count()
}
pub fn data(self: &Self, _index: &QModelIndex, _role: i32) -> QVariant {
QVariant::default()
} }
} }
pub struct GreeterRust { /// A struct which inherits from QAbstractListModel
greeting: Greeting, #[derive(Default)]
language: Language, pub struct AbstractListModelRust {}
}
impl Default for GreeterRust { #[derive(Default)]
fn default() -> Self { pub struct LineCountRust {
Self { pub count: i32,
greeting: Greeting::Hello,
language: Language::English,
}
}
}
use cxx_qt_lib::QString;
impl qobject::Greeter {
fn greet(&self) -> QString {
QString::from(self.greeting.translate(self.language))
}
} }
fn main() { fn main() {