diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 9f1e140..d3f2965 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -28,6 +28,7 @@ qt_add_qml_module(orkmode QML_FILES qml/Main.qml qml/OutlineView.qml + qml/SettingsDialog.qml ) target_link_libraries(orkmode PRIVATE diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 2f4061a..013170a 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -9,6 +9,10 @@ ApplicationWindow { visible: true title: qsTr("orkmode") + // Apply theme font globally + font.family: theme.fontFamily || Qt.application.font.family + font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize + // Apply theme palette to Qt Quick Controls palette.active.window: theme.windowColor palette.active.windowText: theme.windowTextColor @@ -80,6 +84,11 @@ ApplicationWindow { function onConnectedChanged() { root.refreshDocs() } } + // Settings dialog + SettingsDialog { + id: settingsDialog + } + header: ToolBar { background: Rectangle { color: theme.buttonColor } RowLayout { @@ -106,6 +115,12 @@ ApplicationWindow { ToolTip.text: qsTr("Reload") ToolTip.visible: hovered } + ToolButton { + text: "⚙" + onClicked: settingsDialog.open() + ToolTip.text: qsTr("Settings") + ToolTip.visible: hovered + } } } diff --git a/ui/qml/OutlineView.qml b/ui/qml/OutlineView.qml index 62d6d68..1c9ca43 100644 --- a/ui/qml/OutlineView.qml +++ b/ui/qml/OutlineView.qml @@ -11,6 +11,7 @@ TreeView { clip: true readonly property int rowPad: 6 + readonly property int baseFontSize: theme.fontSize > 0 ? theme.fontSize : 12 // Palette for TODO keywords by state. function keywordColor(kw) { @@ -36,7 +37,7 @@ TreeView { // Headline text scales down slightly with depth. function levelFontSize(level) { - return Math.max(13, 19 - level) + return Math.max(tree.baseFontSize - 1, tree.baseFontSize + 5 - level) } selectionModel: ItemSelectionModel {} @@ -79,7 +80,7 @@ TreeView { anchors.centerIn: parent text: model.keyword ?? "" color: tree.keywordColor(model.keyword ?? "") - font.pixelSize: 11 + font.pointSize: tree.baseFontSize - 1 font.bold: true } } @@ -89,7 +90,7 @@ TreeView { visible: (model.priority ?? "") !== "" text: "[#" + (model.priority ?? "") + "]" color: tree.priorityColor(model.priority ?? "") - font.pixelSize: 12 + font.pointSize: tree.baseFontSize font.bold: true } @@ -97,7 +98,7 @@ TreeView { Label { text: (model.title ?? "") color: (model.isDone ?? false) ? theme.placeholderTextColor : theme.textColor - font.pixelSize: tree.levelFontSize(model.level ?? 1) + font.pointSize: tree.levelFontSize(model.level ?? 1) font.strikeout: (model.isDone ?? false) elide: Text.ElideRight Layout.fillWidth: true @@ -108,13 +109,13 @@ TreeView { visible: (model.scheduled ?? "") !== "" text: "⏱ " + (model.scheduled ?? "") color: theme.timestampColor - font.pixelSize: 11 + font.pointSize: tree.baseFontSize - 1 } Label { visible: (model.deadline ?? "") !== "" text: "⚑ " + (model.deadline ?? "") color: theme.deadlineColor - font.pixelSize: 11 + font.pointSize: tree.baseFontSize - 1 } // Tags. @@ -131,7 +132,7 @@ TreeView { anchors.centerIn: parent text: parent.modelData color: theme.tagColor - font.pixelSize: 10 + font.pointSize: tree.baseFontSize - 2 } } } @@ -159,7 +160,7 @@ TreeView { Layout.leftMargin: del.bodyIndent - 10 text: del.sectionBody color: theme.placeholderTextColor - font.pixelSize: 12 + font.pointSize: tree.baseFontSize lineHeight: 1.15 wrapMode: Text.WordWrap textFormat: Text.PlainText diff --git a/ui/qml/SettingsDialog.qml b/ui/qml/SettingsDialog.qml new file mode 100644 index 0000000..78e9621 --- /dev/null +++ b/ui/qml/SettingsDialog.qml @@ -0,0 +1,206 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Dialog { + id: settingsDialog + title: qsTr("Settings") + modal: true + standardButtons: Dialog.Close + width: 450 + height: 400 + + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + + background: Rectangle { + color: theme.windowColor + border.color: theme.midColor + border.width: 1 + radius: 6 + } + + header: Rectangle { + implicitHeight: 40 + color: theme.buttonColor + radius: 6 + + Label { + anchors.centerIn: parent + text: settingsDialog.title + color: theme.buttonTextColor + font.bold: true + font.pointSize: theme.fontSize > 0 ? theme.fontSize + 1 : 13 + } + + // Square off bottom corners + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 6 + color: theme.buttonColor + } + } + + contentItem: ColumnLayout { + spacing: 16 + + // --- Color Scheme --- + GroupBox { + title: qsTr("Color Scheme") + Layout.fillWidth: true + + background: Rectangle { + color: "transparent" + border.color: theme.midColor + border.width: 1 + radius: 4 + y: 12 + } + + label: Label { + x: 8 + text: parent.title + color: theme.textColor + font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 11 + } + + RowLayout { + anchors.fill: parent + spacing: 8 + + ComboBox { + id: schemeCombo + Layout.fillWidth: true + model: theme.availableSchemes + currentIndex: { + let idx = model.indexOf(theme.mode) + return idx >= 0 ? idx : 0 + } + onActivated: theme.mode = currentText + } + } + } + + // --- Font --- + GroupBox { + title: qsTr("Font") + Layout.fillWidth: true + + background: Rectangle { + color: "transparent" + border.color: theme.midColor + border.width: 1 + radius: 4 + y: 12 + } + + label: Label { + x: 8 + text: parent.title + color: theme.textColor + font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 11 + } + + GridLayout { + columns: 2 + anchors.fill: parent + columnSpacing: 12 + rowSpacing: 8 + + Label { text: qsTr("Family:"); color: theme.textColor } + ComboBox { + id: fontFamilyCombo + Layout.fillWidth: true + model: theme.availableFamilies + editable: true + currentIndex: { + let fam = theme.fontFamily || Qt.application.font.family + let idx = model.indexOf(fam) + return idx >= 0 ? idx : 0 + } + onActivated: theme.fontFamily = currentText + onAccepted: theme.fontFamily = editText + } + + Label { text: qsTr("Style:"); color: theme.textColor } + ComboBox { + id: fontStyleCombo + Layout.fillWidth: true + model: theme.availableStyles + currentIndex: { + let st = theme.fontStyle + let idx = model.indexOf(st) + return idx >= 0 ? idx : 0 + } + onActivated: theme.fontStyle = currentText + } + + Label { text: qsTr("Size:"); color: theme.textColor } + SpinBox { + id: fontSizeSpin + from: 6 + to: 72 + value: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize + onValueModified: theme.fontSize = value + } + } + } + + // --- UI Scale --- + GroupBox { + title: qsTr("UI Scale") + Layout.fillWidth: true + + background: Rectangle { + color: "transparent" + border.color: theme.midColor + border.width: 1 + radius: 4 + y: 12 + } + + label: Label { + x: 8 + text: parent.title + color: theme.textColor + font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 11 + } + + RowLayout { + anchors.fill: parent + spacing: 12 + + Slider { + id: scaleSlider + Layout.fillWidth: true + from: 0.5 + to: 3.0 + stepSize: 0.1 + value: theme.uiScale + onMoved: theme.uiScale = value + } + + Label { + text: (theme.uiScale * 100).toFixed(0) + "%" + color: theme.textColor + Layout.preferredWidth: 50 + } + } + } + + // --- Reset buttons --- + RowLayout { + Layout.fillWidth: true + spacing: 8 + + Button { + text: qsTr("Reset Font") + onClicked: theme.resetFont() + } + + Item { Layout.fillWidth: true } + } + } +} diff --git a/ui/src/thememanager.cpp b/ui/src/thememanager.cpp index 5e987c2..11c2153 100644 --- a/ui/src/thememanager.cpp +++ b/ui/src/thememanager.cpp @@ -4,14 +4,19 @@ #include #include #include +#include +#include #include ThemeManager::ThemeManager(QObject *parent) : QObject(parent) , m_mode("system") + , m_fontSize(0) + , m_uiScale(1.0) { loadSettings(); applyPalette(); + applyFont(); } QString ThemeManager::mode() const @@ -341,6 +346,10 @@ void ThemeManager::loadSettings() if (QFile::exists(configFile)) { QSettings settings(configFile, QSettings::IniFormat); m_mode = settings.value("General/mode", "system").toString(); + m_fontFamily = settings.value("Font/family", "").toString(); + m_fontSize = settings.value("Font/size", 0).toInt(); + m_fontStyle = settings.value("Font/style", "").toString(); + m_uiScale = settings.value("General/uiScale", 1.0).toDouble(); } } @@ -352,4 +361,119 @@ void ThemeManager::saveSettings() QSettings settings(configFile, QSettings::IniFormat); settings.setValue("General/mode", m_mode); + settings.setValue("Font/family", m_fontFamily); + settings.setValue("Font/size", m_fontSize); + settings.setValue("Font/style", m_fontStyle); + settings.setValue("General/uiScale", m_uiScale); +} + +// --- Font methods --- + +QString ThemeManager::fontFamily() const +{ + return m_fontFamily; +} + +void ThemeManager::setFontFamily(const QString &family) +{ + if (family == m_fontFamily) + return; + m_fontFamily = family; + saveSettings(); + applyFont(); + emit fontChanged(); +} + +int ThemeManager::fontSize() const +{ + return m_fontSize; +} + +void ThemeManager::setFontSize(int size) +{ + if (size < 6) size = 6; + if (size > 72) size = 72; + if (size == m_fontSize) + return; + m_fontSize = size; + saveSettings(); + applyFont(); + emit fontChanged(); +} + +QString ThemeManager::fontStyle() const +{ + return m_fontStyle; +} + +void ThemeManager::setFontStyle(const QString &style) +{ + if (style == m_fontStyle) + return; + m_fontStyle = style; + saveSettings(); + applyFont(); + emit fontChanged(); +} + +QStringList ThemeManager::availableFamilies() const +{ + return QFontDatabase::families(); +} + +QStringList ThemeManager::availableStyles() const +{ + QString family = m_fontFamily.isEmpty() + ? QGuiApplication::font().family() + : m_fontFamily; + return QFontDatabase::styles(family); +} + +void ThemeManager::resetFont() +{ + m_fontFamily.clear(); + m_fontSize = 0; + m_fontStyle.clear(); + saveSettings(); + applyFont(); + emit fontChanged(); +} + +void ThemeManager::applyFont() +{ + QFont font; + + if (!m_fontFamily.isEmpty()) + font.setFamily(m_fontFamily); + + if (m_fontSize > 0) + font.setPointSize(m_fontSize); + + if (!m_fontStyle.isEmpty()) { + QString family = m_fontFamily.isEmpty() ? font.family() : m_fontFamily; + QFont styled = QFontDatabase::font(family, m_fontStyle, + m_fontSize > 0 ? m_fontSize : font.pointSize()); + font.setWeight(styled.weight()); + font.setItalic(styled.italic()); + } + + QGuiApplication::setFont(font); +} + +// --- HiDPI scaling --- + +qreal ThemeManager::uiScale() const +{ + return m_uiScale; +} + +void ThemeManager::setUiScale(qreal scale) +{ + if (scale < 0.5) scale = 0.5; + if (scale > 4.0) scale = 4.0; + if (qFuzzyCompare(scale, m_uiScale)) + return; + m_uiScale = scale; + saveSettings(); + emit uiScaleChanged(); } diff --git a/ui/src/thememanager.h b/ui/src/thememanager.h index 7497851..b56f13f 100644 --- a/ui/src/thememanager.h +++ b/ui/src/thememanager.h @@ -54,6 +54,16 @@ class ThemeManager : public QObject Q_PROPERTY(QColor timestampColor READ timestampColor NOTIFY paletteChanged) Q_PROPERTY(QColor deadlineColor READ deadlineColor NOTIFY paletteChanged) + // Font properties + Q_PROPERTY(QString fontFamily READ fontFamily WRITE setFontFamily NOTIFY fontChanged) + Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize NOTIFY fontChanged) + Q_PROPERTY(QString fontStyle READ fontStyle WRITE setFontStyle NOTIFY fontChanged) + Q_PROPERTY(QStringList availableFamilies READ availableFamilies CONSTANT) + Q_PROPERTY(QStringList availableStyles READ availableStyles NOTIFY fontChanged) + + // HiDPI scaling + Q_PROPERTY(qreal uiScale READ uiScale WRITE setUiScale NOTIFY uiScaleChanged) + public: explicit ThemeManager(QObject *parent = nullptr); @@ -93,11 +103,29 @@ public: QColor timestampColor() const; QColor deadlineColor() const; + // Font accessors + QString fontFamily() const; + void setFontFamily(const QString &family); + int fontSize() const; + void setFontSize(int size); + QString fontStyle() const; + void setFontStyle(const QString &style); + QStringList availableFamilies() const; + QStringList availableStyles() const; + Q_INVOKABLE void resetFont(); + + // HiDPI scaling + qreal uiScale() const; + void setUiScale(qreal scale); + void applyPalette(); + void applyFont(); signals: void modeChanged(); void paletteChanged(); + void fontChanged(); + void uiScaleChanged(); private: QPalette paletteFromScheme(const QString &path) const; @@ -110,5 +138,9 @@ private: QString m_mode; QPalette m_palette; + QString m_fontFamily; + int m_fontSize; + QString m_fontStyle; + qreal m_uiScale; static constexpr const char *SCHEMES_DIR = "/usr/share/color-schemes"; };