diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a3b91f0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,30 @@
+# HyprlandQuickshellGUI: a replacement for Waybar
+
+## Colors
+### [Matugen](https://github.com/InioX/matugen)
+We use [matugen](https://github.com/InioX/matugen) for the colors. With these Templates for Some Applications we use: [maguten-config](https://git.honney333.work/honney/matugen-config)
+
+## Wallpaper
+We use a custom directory by deafault to save all your Wallpapers:
+```
+$HOME/Pictures/Wallpapers/
+```
+### Compatible Wallpaper Software:
+- [hyprpaper](https://github.com/hyprwm/hyprpaper)
+- [swww](https://github.com/LGFae/swww)
+- [awww (comming)](https://codeberg.org/LGFae/awww)
+
+### Recommended Software for some stuff:
+- [ImageMagick (resizing & blurring)](https://github.com/ImageMagick/ImageMagick)
+
+## Other Software:
+### Logout:
+- [wlogout](https://github.com/ArtsyMacaw/wlogout)
+- [nmcli (Recommended for Wifi)](https://github.com/ushiboy/nmcli)
+
+## TO Enable Sub Programs:
+Add the Lines to your ```~/.config/hypr/hyprland.conf``` keep in mind that you might need to change the Keybind.
+### Wallpaper Picker
+```bind = $mainMod SHIFT, W, global, quickshell:Wallpaper```
+### JLearner
+```bind = $mainMod SHIFT, J, global, quickshell:JLearner```
diff --git a/colors/example_background.png b/colors/example_background.png
new file mode 100644
index 0000000..aa1da30
Binary files /dev/null and b/colors/example_background.png differ
diff --git a/colors/example_colors.qml b/colors/example_colors.qml
new file mode 100644
index 0000000..dfd8f8c
--- /dev/null
+++ b/colors/example_colors.qml
@@ -0,0 +1,57 @@
+pragma Singleton
+import QtQuick 2.15
+
+QtObject {
+
+ // ===== PRIMARY =====
+ readonly property color primary: "#cbcb77"
+ readonly property color primary_fg: "#323200"
+ readonly property color primaryContainer: "#494900"
+ readonly property color primaryContainer_fg: "#e7e790"
+
+ // ===== SECONDARY =====
+ readonly property color secondary: "#cac8a5"
+ readonly property color secondary_fg: "#323218"
+ readonly property color secondaryContainer: "#48482d"
+ readonly property color secondaryContainer_fg: "#e6e4bf"
+
+ // ===== TERTIARY =====
+ readonly property color tertiary: "#a4d0bd"
+ readonly property color tertiary_fg: "#0a372a"
+ readonly property color tertiaryContainer: "#254e40"
+ readonly property color tertiaryContainer_fg: "#bfecd9"
+
+ // ===== ERROR =====
+ readonly property color error: "#ffb4ab"
+ readonly property color error_fg: "#690005"
+ readonly property color errorContainer: "#93000a"
+ readonly property color errorContainer_fg: "#ffdad6"
+
+ // ===== BACKGROUND / SURFACE =====
+ readonly property color background: "#14140c"
+ readonly property color background_fg: "#e6e3d5"
+
+ readonly property color surface: "#14140c"
+ readonly property color surface_fg: "#e6e3d5"
+
+ readonly property color surfaceVariant: "#48473a"
+ readonly property color surfaceVariant_fg: "#c9c7b6"
+
+ // ===== SURFACE CONTAINERS =====
+ readonly property color surfaceContainerLowest: "#0f0f07"
+ readonly property color surfaceContainerLow: "#1c1c14"
+ readonly property color surfaceContainer: "#202018"
+ readonly property color surfaceContainerHigh: "#2b2a22"
+ readonly property color surfaceContainerHighest: "#36352c"
+
+ // ===== OUTLINE / UTILITY =====
+ readonly property color outline: "#939182"
+ readonly property color outlineVariant: "#48473a"
+ readonly property color shadowColor: "#000000"
+ readonly property color scrimColor: "#000000"
+
+ // ===== INVERSE =====
+ readonly property color inverseSurface: "#e6e3d5"
+ readonly property color inverseSurface_fg: "#313128"
+ readonly property color inversePrimary: "#616219"
+}
diff --git a/colors/icons/correct.svg b/colors/icons/correct.svg
new file mode 100644
index 0000000..1f9b909
--- /dev/null
+++ b/colors/icons/correct.svg
@@ -0,0 +1,14 @@
+
diff --git a/colors/icons/wrong.svg b/colors/icons/wrong.svg
new file mode 100644
index 0000000..66aab82
--- /dev/null
+++ b/colors/icons/wrong.svg
@@ -0,0 +1,24 @@
+
diff --git a/modules/bar/Bar.qml b/modules/bar/Bar.qml
index 6ff84ee..901b4ee 100644
--- a/modules/bar/Bar.qml
+++ b/modules/bar/Bar.qml
@@ -77,8 +77,5 @@ PanelWindow {
PowerButton {}
}
-
- // TODO: Somewhere add Tray
-
}
}
diff --git a/modules/bar/scripts/Apps.sh b/modules/bar/scripts/Apps.sh
new file mode 100755
index 0000000..7ccb403
--- /dev/null
+++ b/modules/bar/scripts/Apps.sh
@@ -0,0 +1,127 @@
+#!/bin/bash
+
+ID="$1"
+
+if [[ -z "$ID" ]]; then
+ echo "Usage: $0 Number"
+ exit 1
+fi
+# Command Helper: hyprctl clients -j | jq '.[] | select(.workspace.id == 1)'
+# Icons: https://www.nerdfonts.com/cheat-sheet
+declare -A MAP=(
+ # Youtube
+ ["Grayjay"]=""
+ # Browser
+ ["Ablaze Floorp"]=""
+ ["LibreWolf"]=""
+ ["midori"]=""
+ ["brave-browser"]=""
+ ["chromium"]=""
+ # E-Mail
+ ["org.mozilla.Thunderbird"]=""
+ # Terminals
+ ["kitty"]=""
+ ["Alacritty"]=""
+ # Videoplayer
+ ["VLC media player"]=""
+ ["mpv"]=""
+ ["ffplay"]=""
+ # Chats
+ ["discord"]=""
+ ["vesktop"]=""
+ ["Signal"]=""
+ ["xyz.chatboxapp.app"]=""
+ # Coding
+ ["Visual Studio Code"]=""
+ ["VSCodium"]=""
+ ["jetbrains-idea"]=""
+ ["jetbrains-studio"]=""
+ # Text Editor
+ ["mousepad"]=""
+ # Password
+ ["org.keepassxc.KeePassXC"]=""
+ ["Yubico Authenticator"]=""
+ # Cloud
+ ["com.nextcloud.desktopclient.nextcloud"]=""
+ # App Store
+ ["Octopi"]=""
+ # Games
+ ["steam"]=""
+ ["org.prismlauncher.PrismLauncher"]=""
+ ["Minecraft* 1.21.11"]=""
+ ["game"]=""
+ # Digital Arts
+ ["Blender"]=""
+ ["Krita"]=""
+ # VM
+ ["virt-manager"]=""
+ ["VirtualBox Manager"]=""
+ ["remote-viewer"]=""
+ # Audio
+ ["qpwgraph"]=""
+ ["Carla"]=""
+ # Video
+ ["com.obsproject.Studio"]=""
+ ["hyprland-share-picker"]=""
+ # Document Editor/Viewer
+ ["libreoffice-startcenter"]=""
+ ["libreoffice-base"]=""
+ ["libreoffice-calc"]=""
+ ["libreoffice-draw"]=""
+ ["libreoffice-impress"]=""
+ ["libreoffice-math"]=""
+ ["libreoffice-writer"]=""
+ ["com.github.flxzt.rnote"]=""
+ ["com.github.xournalpp.xournalpp"]=""
+ ["qpdfview.local.qpdfview"]=""
+ # Hardware
+ ["Mission Center"]=""
+ ["QDirStat"]=""
+ ["Disks"]=""
+ # File Manager
+ ["nemo"]=""
+ ["org.gnome.Nautilus"]=""
+ ["org.kde.dolphin"]=""
+ ["xdg-desktop-portal-gtk"]=""
+ # Settings
+ ["nwg-look"]="" # gtk-settings
+ ["qt5ct"]=""
+ ["qt6ct"]=""
+ ["sddm-conf"]=""
+ ["blueman-manager"]=""
+ ["nm-connection-editor"]=""
+ # Calculator
+ ["Calculator"]=""
+ # VPN
+ ["Proton VPN"]=""
+)
+
+# APPS=$(hyprctl clients -j | jq -r ".[] | select(.workspace.id == $ID) | .initialTitle")
+
+mapfile -t APPS < <(
+ hyprctl clients -j |
+ jq -r ".[] | select(.workspace.id == $ID) | .initialTitle"
+)
+
+mapfile -t APPS_CLASS < <(
+ hyprctl clients -j |
+ jq -r ".[] | select(.workspace.id == $ID) | .initialClass"
+)
+
+for i in "${!APPS[@]}"; do
+ if [[ -n "${MAP["${APPS[$i]}"]}" ]]; then
+ APPS[$i]="${MAP["${APPS[$i]}"]}"
+ elif [[ -n "${MAP["${APPS_CLASS[$i]}"]}" ]]; then
+ APPS[$i]="${MAP["${APPS_CLASS[$i]}"]}"
+ else
+ APPS[$i]="${APPS_CLASS[$i]}|${APPS[$i]}"
+ fi
+done
+
+# for app in "${APPS[@]}"; do
+# echo "$app"
+# done
+
+RESULT="$ID $(printf '%s ' "${APPS[@]}")"
+RESULT=${RESULT% } # remove trailing space
+echo "$RESULT"
\ No newline at end of file
diff --git a/modules/bar/widgets/Clock.qml b/modules/bar/widgets/Clock.qml
index 530ff9d..12d9d10 100644
--- a/modules/bar/widgets/Clock.qml
+++ b/modules/bar/widgets/Clock.qml
@@ -4,34 +4,73 @@ import Quickshell.Hyprland
import "../../../colors"
Rectangle { //workspace
- width: timeDisplay.width + 16
+ width: timeDisplay.width + 8 + dateDisplay.width + 16
height: timeDisplay.height + 16
radius: 4
color: Colors.primaryContainer
border.color: Colors.primaryContainer_fg
border.width: 2
+ MouseArea {
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+
+ Row {
+ id: row
+ spacing: 8
+ anchors.centerIn: parent
- Text {
- id: timeDisplay
+ Text {
+ id: dateDisplay
+ property string currentDate: ""
- property string currentTime: ""
+ text: currentDate
+ color: Colors.primary
+ font.pixelSize: 18
+ font.family: "Inter, sans-serif"
- text: currentTime
- color: Colors.primary
- font.pixelSize: 18
- font.family: "Inter, sans-serif"
-
- anchors.centerIn: parent
-
- //Update time every second
- Timer {
- interval: 1000
- running: true
- repeat: true
- onTriggered: {
- var now = new Date()
- timeDisplay.currentTime = Qt.formatTime(now, "hh:mm:ss")
+ Timer {
+ id: dateTimer
+ interval: 60000
+ running: false
+ repeat: true
+ triggeredOnStart: true
+ onTriggered: {
+ var now = new Date()
+ dateDisplay.currentDate = Qt.formatDate(now, "dd.MM.yyyy")
+ }
+ }
}
+
+ Text {
+ id: timeDisplay
+ property string currentTime: ""
+
+ text: currentTime
+ color: Colors.primary
+ font.pixelSize: 18
+ font.family: "Inter, sans-serif"
+
+ Component.onCompleted: {
+ var now = new Date()
+ currentTime = Qt.formatTime(now, "hh:mm:ss")
+ }
+
+ Timer {
+ interval: 1000
+ running: true
+ repeat: true
+ onTriggered: {
+ var now = new Date()
+ timeDisplay.currentTime = Qt.formatTime(now, "hh:mm:ss")
+ }
+ }
+ }
+ }
+ onClicked: {
+ dateDisplay.currentDate = "";
+ dateTimer.running = !dateTimer.running;
}
}
}
\ No newline at end of file
diff --git a/modules/bar/widgets/Network.qml b/modules/bar/widgets/Network.qml
index 1a12c68..e8d45fe 100644
--- a/modules/bar/widgets/Network.qml
+++ b/modules/bar/widgets/Network.qml
@@ -6,7 +6,7 @@ import "../../../colors"
MouseArea {
id: networkButton
- width: output.width + 16
+ width: bar.height
height: bar.height
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
@@ -19,7 +19,9 @@ MouseArea {
anchors.fill: parent
color: networkButton.containsMouse ? Colors.tertiary : Qt.alpha(Colors.tertiaryContainer, 0.25)
radius: 6
- width: output.width + 16
+ border.width: 2
+ border.color: networkButton.containsMouse ? Colors.tertiaryContainer_fg
+ : Colors.secondaryContainer_fg
Text {
id: output
@@ -37,7 +39,7 @@ MouseArea {
stdout: StdioCollector {
onStreamFinished: {
output.text = this.text.trim()
- console.log("networkScript output:", this.text.trim())
+ // console.log("networkScript output:", this.text.trim())
}
}
}
diff --git a/modules/bar/widgets/Tray.qml b/modules/bar/widgets/Tray.qml
index dc16fe1..c814402 100644
--- a/modules/bar/widgets/Tray.qml
+++ b/modules/bar/widgets/Tray.qml
@@ -7,85 +7,145 @@ import "../../../colors"
Row { // Workspace Row
id: trayRow
- spacing: 8
+ spacing: 4
Repeater {
model: SystemTray.items
-
- Rectangle { //trayItem
- id: trayItem
- width: bar.height
- height: bar.height
- radius: 4
- color: "transparent"
- border.width: 2
-
- Process {
- id: steamRunner
- command: ["steam", "steam://open/main"]
- running: false
+ Column{
+ Rectangle{
+ width: iconImage.width + 8
+ height: (bar.height-trayItem.height)/2
+ color: "transparent"
}
+ Rectangle { //trayItem
+ id: trayItem
+ width: iconImage.width + 8
+ height: iconImage.height + 8
+ radius: 4
+ color: "transparent"
+ border.width: 2
- MouseArea {
- id: workspaceButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- anchors.fill: parent
- acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
- onClicked: (mouse) => {
- switch (mouse.button) {
- case Qt.LeftButton:
- if (!modelData.onlyMenu) {
- modelData.activate()
- console.log('Activcate function of ', modelData.title)
+ Process {
+ id: steamRunner
+ command: ["steam", "steam://open/main"]
+ running: false
+ }
+
+ MouseArea {
+ id: workspaceButton
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+ onClicked: (mouse) => {
+ switch (mouse.button) {
+ case Qt.LeftButton:
+ if (!modelData.onlyMenu) {
+ modelData.activate()
+ console.log('Activcate function of ', modelData.title)
+ }
+ if (modelData.title.trim()==="Steam") {
+ steamRunner.startDetached()
+ }
+ break
+ case Qt.MiddleButton:
+ modelData.secondaryActivate()
+ break
+ // case Qt.RightButton:
+ // if (modelData.hasMenu) {
+ // console.log('Activcate menu of ', modelData.title)
+ // menuAnchor.open()
+ // }
+ // break
}
- if (modelData.title.trim()==="Steam") {
- steamRunner.startDetached()
- }
- break
- case Qt.MiddleButton:
- modelData.secondaryActivate()
- break
- // case Qt.RightButton:
- // if (modelData.hasMenu) {
- // console.log('Activcate menu of ', modelData.title)
- // menuAnchor.open()
+ }
+
+ // QsMenuAnchor {
+ // id: menuAnchor
+ // menu: modelData.menu
+ // anchor.window: menuHost
+ // }
+
+ Rectangle {
+ anchors.fill: parent
+ color: workspaceButton.containsMouse ? Colors.tertiaryContainer
+ : modelData.active ? Colors.primaryContainer
+ : Colors.secondaryContainer
+ border.color: workspaceButton.containsMouse ? Colors.tertiaryContainer_fg
+ : modelData.active ? Colors.primaryContainer_fg
+ : Colors.secondaryContainer_fg
+ radius: 6
+ }
+
+ }
+
+ Image { // Image
+ id: iconImage
+ // source: modelData.icon; Does not work for some tray icons
+ fillMode: Image.PreserveAspectCrop
+
+ anchors.centerIn: parent
+
+ width: bar.height*0.5
+ height: bar.height*0.5
+
+ // Component.onCompleted: {
+ // // Default to the original icon
+ // let src = modelData.icon;
+ // // console.log("########## icon: " + modelData.icon)
+
+ // // Check if there is a "?" in the URL (custom path)
+ // if (src.includes('goxlr')) {
+ // let pathMatch = src.match(/path=([^&]+)/);
+ // if (pathMatch && pathMatch[1]) {
+
+ // let folderPath = pathMatch[1];
+ // // Build a file:// URL pointing to the actual icon file
+ // let segments = folderPath.split('/');
+ // let fileName = segments[segments.length - 1] + "-icon.png";
+ // console.log("########## filename: " + fileName)
+ // console.log("########## icon: " + modelData.icon)
+ // src = "file://" + pathMatch[1] + "/" + fileName;
+ // }
// }
- // break
+
+ // // Set the resolved source
+ // iconImage.source = src;
+
+ // // console.log("Resolved icon source:", src);
+ // }
+ asynchronous: true
+ cache: true
+ }
+
+ Timer {
+ interval: 10000
+ running: true
+ repeat: true
+ triggeredOnStart: true
+ onTriggered: {
+ let src = modelData.icon;
+ // console.log("########## icon: " + modelData.icon)
+
+ // Check if there is a "?" in the URL (custom path)
+ if (src.includes('goxlr')) {
+ let pathMatch = src.match(/path=([^&]+)/);
+ if (pathMatch && pathMatch[1]) {
+
+ let folderPath = pathMatch[1];
+ // Build a file:// URL pointing to the actual icon file
+ let segments = folderPath.split('/');
+ let fileName = segments[segments.length - 1] + "-icon.png";
+ // console.log("########## filename: " + fileName)
+ // console.log("########## icon: " + modelData.icon)
+ src = "file://" + pathMatch[1] + "/" + fileName;
+ }
+ }
+
+ // Set the resolved source
+ iconImage.source = src;
}
}
-
- // QsMenuAnchor {
- // id: menuAnchor
- // menu: modelData.menu
- // anchor.window: menuHost
- // }
-
- Rectangle {
- anchors.fill: parent
- color: workspaceButton.containsMouse ? Colors.tertiaryContainer
- : modelData.active ? Colors.primaryContainer
- : Colors.secondaryContainer
- border.color: workspaceButton.containsMouse ? Colors.tertiaryContainer_fg
- : modelData.active ? Colors.primaryContainer_fg
- : Colors.secondaryContainer_fg
- radius: 6
- }
-
- }
-
- Image { // Image
- id: iconImage
- source: modelData.icon
- fillMode: Image.PreserveAspectCrop
-
- anchors.centerIn: parent
-
- width: bar.height*0.75
- height: bar.height*0.75
-
- asynchronous: true
- cache: true
}
}
}
diff --git a/modules/bar/widgets/Workspaces.qml b/modules/bar/widgets/Workspaces.qml
index 5a35069..f03edc4 100644
--- a/modules/bar/widgets/Workspaces.qml
+++ b/modules/bar/widgets/Workspaces.qml
@@ -1,6 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Hyprland
+import Quickshell.Io
import "../../../colors"
Row { // Workspace Row
@@ -9,48 +10,84 @@ Row { // Workspace Row
required property HyprlandMonitor thisMonitor
spacing: 8
+ // Get infos of Type Unknown
+ // Repeater {
+ // model: Hyprland.workspaces
+
+ // Rectangle {
+ // Component.onCompleted: {
+ // console.log("roles:")
+ // console.log("id:", modelData.id)
+ // console.log("monitor:", modelData.monitor)
+ // console.log("monitorId:", modelData.monitorId)
+ // console.log("name:", modelData.name)
+ // }
+ // }
+ // }
+
Repeater { // repeats workspaces
model: Hyprland.workspaces
+ Loader {
+ active: modelData.monitor === thisMonitor
+ sourceComponent: Rectangle { //workspace
+ // visible: modelData.monitor === thisMonitor
+ width: (workspaceNum.width + 16) < (workspaceNum.height + 8) ? workspaceNum.height + 8 : workspaceNum.width + 16
+ height: workspaceNum.height + 8
+ radius: 4
+ color: workspaceButton.containsMouse ? Colors.tertiaryContainer
+ : modelData.active ? Colors.primaryContainer
+ : Colors.secondaryContainer
+ border.color: workspaceButton.containsMouse ? Colors.tertiaryContainer_fg
+ : modelData.active ? Colors.primaryContainer_fg
+ : Colors.secondaryContainer_fg
+ border.width: 2
- Rectangle { //workspace
- visible: modelData.monitor === thisMonitor
- width: workspaceNum.width + 26
- height: workspaceNum.height + 8
- radius: 4
- color: workspaceButton.containsMouse ? Colors.tertiaryContainer
- : modelData.active ? Colors.primaryContainer
- : Colors.secondaryContainer
- border.color: workspaceButton.containsMouse ? Colors.tertiaryContainer_fg
- : modelData.active ? Colors.primaryContainer_fg
- : Colors.secondaryContainer_fg
- border.width: 2
-
- MouseArea {
- id: workspaceButton
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- anchors.fill: parent
- onClicked: Hyprland.dispatch("workspace " + modelData.id)
-
- Rectangle {
+ MouseArea {
+ id: workspaceButton
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
anchors.fill: parent
- color: "transparent"
- radius: 6
+ onClicked: Hyprland.dispatch("workspace " + modelData.id)
+
+ Rectangle {
+ anchors.fill: parent
+ color: "transparent"
+ radius: 6
+ }
+ }
+
+ Text {
+ id: workspaceNum
+ text: modelData.id
+ anchors.centerIn: parent
+ color: workspaceButton.containsMouse ? Colors.tertiary
+ : modelData.active ? Colors.primary
+ : Colors.secondary
+ font.pixelSize: 18
+ font.family: "Inter, sans-serif"
+ }
+
+ Process {
+ id: workspaceContent
+ command: ["sh", "-c", Quickshell.env("HOME") + "/.config/quickshell/modules/bar/scripts/Apps.sh " + modelData.id]
+ running: true
+
+ stdout: StdioCollector {
+ onStreamFinished: {
+ workspaceNum.text = this.text
+ // console.log("workspaceScript output:", this.text)
+ }
+ }
+ }
+
+ Timer {
+ interval: 1000
+ running: true
+ repeat: true
+ onTriggered: workspaceContent.running = true
}
}
-
- Text {
- id: workspaceNum
- text: modelData.id
- anchors.centerIn: parent
- color: workspaceButton.containsMouse ? Colors.tertiary
- : modelData.active ? Colors.primary
- : Colors.secondary
- font.pixelSize: 18
- font.family: "Inter, sans-serif"
- }
}
-
}
Text {
diff --git a/modules/jl/JLearner.qml b/modules/jl/JLearner.qml
new file mode 100644
index 0000000..1f207cd
--- /dev/null
+++ b/modules/jl/JLearner.qml
@@ -0,0 +1,141 @@
+import QtQuick
+import Quickshell
+import QtQuick.Controls
+import Quickshell.Wayland
+import Quickshell.Hyprland
+import Quickshell.Io
+import "../../colors"
+import "./modules"
+
+PanelWindow {
+ id: learner
+ implicitWidth: screen.width * 0.75
+ implicitHeight: screen.height * 0.5
+ color: "transparent"
+ visible: true
+ focusable: true
+
+ Component.onCompleted: {
+ semiJLoader.all_invisible()
+ semiJLoader.switchModeChooser()
+ }
+
+ FocusScope {
+ id: jLearnerScope
+ focus: true
+
+ Keys.onReleased: function(event) {
+ if (event.key === Qt.Key_Escape) {
+ console.log("Pressed Escape, closing JLearner")
+ jLearnerLoader.active = false
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ color: "lightblue"
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ radius: 10
+ color: Qt.alpha(Colors.surfaceContainerLowest, 0.5)
+
+ border.width: 2
+ border.color: Colors.outlineVariant
+
+ clip: true
+
+ Rectangle{
+ width: parent.width - 20
+ height: parent.height - 20
+ anchors.centerIn: parent
+ color: "transparent"
+
+ Column {
+ id: learnerWindow
+ anchors.fill: parent
+ spacing: 10
+
+ Rectangle {
+ id: head
+ width: parent.width
+ height: parent.height * 0.1
+ color: Colors.tertiaryContainer
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.tertiaryContainer_fg
+
+ Text {
+ text: "Language Learner"
+ anchors.centerIn: parent
+ font.pixelSize: parent.height * 0.8
+ color: Colors.tertiary
+ }
+ }
+
+ Rectangle{
+ id: semiJLoader
+ width: parent.width
+ height: parent.height * 0.9 - parent.spacing
+
+ color: "transparent"
+
+ function all_invisible() {
+ languageChooser.visible = false
+ kanaVoicing.visible = false
+ voicingKana.visible = false
+ learnerResult.visible = false
+ }
+
+ function switchModeChooser() {
+ all_invisible()
+ languageChooser.visible = true
+ languageChooser.start()
+ }
+
+ function switch_KanaVoicing() {
+ all_invisible()
+ kanaVoicing.visible = true
+ kanaVoicing.start()
+ }
+
+ function switch_VoicingKana() {
+ all_invisible()
+ voicingKana.visible = true
+ voicingKana.start()
+ }
+
+ function switchResult(result, mode, results) {
+ all_invisible()
+ console.log("Result: " + result)
+ console.log("Mode: " + mode)
+ learnerResult.visible = true
+ learnerResult.result = result
+ learnerResult.mode = mode
+ learnerResult.results = results
+ learnerResult.start()
+ }
+
+ ModeChooser {
+ id: languageChooser
+ }
+
+ KanaVoicing {
+ id: kanaVoicing
+ }
+
+ VoicingKana {
+ id: voicingKana
+ }
+
+ JLearnerResult {
+ id: learnerResult
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/jl/JLearnerLoader.qml b/modules/jl/JLearnerLoader.qml
new file mode 100644
index 0000000..7754e54
--- /dev/null
+++ b/modules/jl/JLearnerLoader.qml
@@ -0,0 +1,29 @@
+import QtQuick
+import Quickshell
+import Quickshell.Hyprland
+import Quickshell.Io
+
+
+Item { // container, invisible
+ id: jLoader
+
+ Loader {
+ id: jLearnerLoader
+ active: false
+ sourceComponent: JLearner { id: jLearner }
+ }
+
+ GlobalShortcut {
+ name: "JLearner"
+ description: "Open Japanese Learner"
+
+ onPressed: {
+ console.log("Pressed Meta+Shift+J (Hyprland global shortcut)")
+ jLearnerLoader.active = !jLearnerLoader.active
+ }
+ }
+
+ function closeJLearner() {
+ jLearnerLoader.active = false
+ }
+}
\ No newline at end of file
diff --git a/modules/jl/modules/JLearnerResult.qml b/modules/jl/modules/JLearnerResult.qml
new file mode 100644
index 0000000..4f24b6d
--- /dev/null
+++ b/modules/jl/modules/JLearnerResult.qml
@@ -0,0 +1,293 @@
+import QtQuick
+import QtQuick.Controls
+import "../../../colors"
+
+Item { // Chooser
+ id: learnerResult
+ width: parent.width
+ height: parent.height
+ anchors.margins: 15
+ visible: false
+
+ function switch_KanaVoicing() {
+ parent.switch_KanaVoicing()
+ }
+
+ function start() {
+ }
+
+ property real result: 0
+ property string mode: ""
+ property var results: createResults(10)
+
+ function createResults(n) {
+ var arr = []
+ for (var i = 0; i < n; i++) {
+ arr.push({
+ kana: "あ",
+ correctness: false,
+ answer: "a",
+ correct: "a"
+ })
+ }
+ return arr
+ }
+
+ Column {
+ anchors.fill: parent
+ spacing: 10
+
+ Rectangle {
+ id: learnerResultUpperText
+ color: Colors.primaryContainer
+ width: parent.width
+ height: parent.height * 0.1
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text {
+ id: mainText
+ text: "Ergebnisse:"
+ anchors.centerIn: parent
+ font.pixelSize: parent.height * 0.6
+ color: Colors.primary
+ }
+ }
+
+ Row {
+ width: parent.width
+ height: parent.height - learnerResultUpperText.height - parent.spacing
+ spacing: 10
+ Rectangle {
+ width: parent.width * 0.25
+ height: parent.height
+ color: "transparent"
+ Column{
+ width: parent.width
+ height: parent.height
+ spacing: 10
+
+ Repeater{
+ model: results
+
+ Row{
+ width: parent.width
+ height: parent.height/10 - parent.spacing * 0.9
+ spacing: 10
+
+ Rectangle{
+ width: parent.height
+ height: parent.height
+ color: Colors.primaryContainer
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text{
+ text: modelData.kana
+ width: parent.width
+ height: parent.height
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: -parent.height * 0.125
+ anchors.horizontalCenterOffset: parent.width * 0.1
+ font.pixelSize: parent.height * 0.8
+ color: modelData.correctness ? "green" : "red"
+ }
+ }
+
+ Rectangle{
+ width: parent.height
+ height: parent.height
+ color: Colors.primaryContainer
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Image {
+ source: modelData.correctness ? "../../../colors/icons/correct.svg" : "../../../colors/icons/wrong.svg"
+ anchors.centerIn: parent
+ width: parent.height*0.8
+ height: parent.height*0.8
+
+ smooth: true
+ sourceSize.width: width
+ sourceSize.height: height
+ }
+ }
+
+ Rectangle{
+ width: parent.width - parent.height *2 - parent.spacing*2
+ height: parent.height
+ color: Colors.primaryContainer
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text{
+ text: "[" + modelData.correct + "]"
+ width: parent.width
+ height: parent.height
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: 10
+ font.pixelSize: parent.height * 0.8
+ color: modelData.correctness ? "green" : "red"
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle{
+ width: parent.width * 0.75
+ height: parent.height
+ color: "transparent"
+ Column{
+ width: parent.width
+ height: parent.height
+ spacing: 10
+ Row {
+ spacing: 10
+ width: parent.width
+ height: parent.height * 4 / 10 - parent.spacing * 2 / 3
+
+ Rectangle {
+ color: "transparent"
+ width: (parent.width - (learnerLastResultKanaBG.width + learnerLastResultTextBG.width))/2
+ height: parent.height
+ }
+
+ Rectangle {
+ id: learnerLastResultKanaBG
+ color: results[results.length-1].correctness ? "green" : "red"
+ width: parent.height - parent.spacing
+ height: parent.height
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text {
+ id: learnerLastResultKana
+ text: results[results.length-1].kana
+ width: parent.width
+ height: parent.height
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: -parent.height * 0.1
+ font.pixelSize: parent.height * 0.8
+ color: Colors.primary
+ }
+ }
+
+ Rectangle {
+ id: learnerLastResultTextBG
+ color: results[results.length-1].correctness ? "green" : "red"
+ width: learnerLastResultText.width + 2 * parent.spacing
+ height: parent.height
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text {
+ id: learnerLastResultText
+ text: "[" + results[results.length-1].correct + "]"
+ anchors.centerIn: parent
+ font.pixelSize: learnerLastResultKana.font.pixelSize/2
+ color: Colors.primary
+ }
+ }
+ }
+
+ Rectangle {
+ id: learnerResultTextBG
+ color: Colors.primaryContainer
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: parent.width
+ height: parent.height * 3 / 10 - parent.spacing * 2 / 3
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text {
+ id: learnerResultText
+ text: result*100 + "% Richtig"
+ width: parent.width
+ horizontalAlignment: Text.AlignHCenter
+ font.pixelSize: Math.min(parent.width / text.length * 1.2, parent.height * 0.8)
+ color: Colors.primary
+ }
+ }
+
+ Row {
+ width: parent.width
+ height: parent.height * 3 / 10 - parent.spacing * 2 / 3
+ spacing: 10
+
+ MouseArea {
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ width: parent.width / 2 - parent.spacing
+ height: parent.height
+
+ onClicked: {
+ console.log("Choosing White Window")
+ mode === "KanaVoicing" ? learnerResult.parent.switch_KanaVoicing() : learnerResult.parent.switch_VoicingKana()
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ color: parent.containsMouse ? Colors.secondaryContainer_fg : Colors.secondaryContainer
+
+ radius: 10
+ border.width: 2
+ border.color: parent.containsMouse ? Colors.secondaryContainer : Colors.secondaryContainer_fg
+
+ Text {
+ text: "Again"
+ anchors.centerIn: parent
+ font.pixelSize: Math.min(parent.width / text.length * 1.2, parent.height * 0.8)
+ color: parent.parent.containsMouse ? Colors.secondary_fg : Colors.secondary
+ }
+ }
+ }
+
+ MouseArea {
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ width: parent.width / 2 - parent.spacing
+ height: parent.height
+
+ onClicked: {
+ console.log("Choosing Black Window")
+ learnerResult.parent.switchModeChooser()
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ color: parent.containsMouse ? Colors.secondaryContainer_fg : Colors.secondaryContainer
+
+ radius: 10
+ border.width: 2
+ border.color: parent.containsMouse ? Colors.secondaryContainer : Colors.secondaryContainer_fg
+
+ Text {
+ text: "Menu"
+ anchors.centerIn: parent
+ font.pixelSize: Math.min(parent.width / text.length * 1.2, parent.height * 0.8)
+ color: parent.parent.containsMouse ? Colors.secondary_fg : Colors.secondary
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/jl/modules/KanaData.qml b/modules/jl/modules/KanaData.qml
new file mode 100644
index 0000000..357b906
--- /dev/null
+++ b/modules/jl/modules/KanaData.qml
@@ -0,0 +1,80 @@
+import QtQuick
+
+QtObject {
+ property var kana: [
+ { hira: "あ", voice: ["a"], known: true },
+ { hira: "い", voice: ["i"], known: true },
+ { hira: 'う', voice: ["u"], known: true },
+ { hira: 'え', voice: ["e"], known: true },
+ { hira: 'お', voice: ["o"], known: true },
+ { hira: 'か', voice: ["ka"], known: true },
+ { hira: 'が', voice: ["ga"], known: true },
+ { hira: 'き', voice: ["ki"], known: true },
+ { hira: 'ぎ', voice: ["gi"], known: true },
+ { hira: 'く', voice: ["ku"], known: true },
+ { hira: 'ぐ', voice: ["gu"], known: true },
+ { hira: 'け', voice: ["ke"], known: true },
+ { hira: 'げ', voice: ["ge"], known: true },
+ { hira: 'こ', voice: ["ko"], known: true },
+ { hira: 'ご', voice: ["go"], known: true },
+ { hira: 'さ', voice: ["sa"], known: true },
+ { hira: 'ざ', voice: ["za"], known: true },
+ { hira: 'し', voice: ["si", "shi"], known: true },
+ { hira: 'じ', voice: ["zi", "ji"], known: true },
+ { hira: 'す', voice: ["su"], known: true },
+ { hira: 'ず', voice: ["zu"], known: true },
+ { hira: 'せ', voice: ["se"], known: true },
+ { hira: 'ぜ', voice: ["ze"], known: true },
+ { hira: 'そ', voice: ["so"], known: true },
+ { hira: 'ぞ', voice: ["zo"], known: true },
+ { hira: 'た', voice: ["ta"], known: true },
+ { hira: 'だ', voice: ["da"], known: true },
+ { hira: 'ち', voice: ["ti", "shi"], known: true },
+ { hira: 'ぢ', voice: ["di", "ji"], known: true },
+ { hira: 'つ', voice: ["tu", "tsu"], known: true },
+ { hira: 'づ', voice: ["du", "zu"], known: true },
+ { hira: 'て', voice: ["te"], known: true },
+ { hira: 'で', voice: ["de"], known: true },
+ { hira: 'と', voice: ["to"], known: true },
+ { hira: 'ど', voice: ["do"], known: true },
+ { hira: 'な', voice: ["na"], known: true },
+ { hira: 'に', voice: ["ni"], known: true },
+ { hira: 'ぬ', voice: ["nu"], known: false },
+ { hira: 'ね', voice: ["ne"], known: false },
+ { hira: 'の', voice: ["no"], known: false },
+ { hira: 'は', voice: ["ha"], known: false },
+ { hira: 'ば', voice: ["ba"], known: false },
+ { hira: 'ぱ', voice: ["pa"], known: false },
+ { hira: 'ひ', voice: ["hi"], known: false },
+ { hira: 'び', voice: ["bi"], known: false },
+ { hira: 'ぴ', voice: ["pi"], known: false },
+ { hira: 'ふ', voice: ["hu"], known: false },
+ { hira: 'ぶ', voice: ["bu"], known: false },
+ { hira: 'ぷ', voice: ["pu"], known: false },
+ { hira: 'へ', voice: ["he"], known: false },
+ { hira: 'べ', voice: ["be"], known: false },
+ { hira: 'ぺ', voice: ["pe"], known: false },
+ { hira: 'ほ', voice: ["ho"], known: false },
+ { hira: 'ぼ', voice: ["bo"], known: false },
+ { hira: 'ぽ', voice: ["po"], known: false },
+ { hira: 'ま', voice: ["ma"], known: false },
+ { hira: 'み', voice: ["mi"], known: false },
+ { hira: 'む', voice: ["mu"], known: false },
+ { hira: 'め', voice: ["me"], known: false },
+ { hira: 'も', voice: ["mo"], known: false },
+ { hira: 'や', voice: ["ya"], known: false },
+ { hira: 'ゆ', voice: ["yu"], known: false },
+ { hira: 'よ', voice: ["yo"], known: false },
+ { hira: 'ら', voice: ["ra"], known: false },
+ { hira: 'り', voice: ["ri"], known: false },
+ { hira: 'る', voice: ["ru"], known: false },
+ { hira: 'れ', voice: ["re"], known: false },
+ { hira: 'ろ', voice: ["ro"], known: false },
+ { hira: 'わ', voice: ["wa"], known: false },
+ { hira: 'ゐ', voice: ["wi"], known: false },
+ { hira: 'ゑ', voice: ["we"], known: false },
+ { hira: 'を', voice: ["wo"], known: false },
+ { hira: 'ん', voice: ["n"], known: true },
+ { hira: 'ゔ', voice: ["vu"], known: false }
+ ]
+}
\ No newline at end of file
diff --git a/modules/jl/modules/KanaVoicing.qml b/modules/jl/modules/KanaVoicing.qml
new file mode 100644
index 0000000..a223344
--- /dev/null
+++ b/modules/jl/modules/KanaVoicing.qml
@@ -0,0 +1,184 @@
+import QtQuick
+import QtQuick.Controls
+import "../../../colors"
+
+Item { // Kana_Try
+ id: kanaVoicing
+ width: parent.width
+ height: parent.height
+ anchors.margins: 15
+ visible: false
+
+ // Component.onCompleted: {
+ // randomKnownKana()
+ // }
+
+ function start() {
+ kanaVoicingAnswer.focus = true
+ roundNum = 0
+ randomKnownKana()
+ }
+
+ KanaData {
+ id: kanaData
+ }
+
+ property var currentKana: []
+
+ property int roundNum: 0
+ property int correct: 0
+ property int maxRoundNum: 10
+
+ property var pastKana: []
+ property var pastCorrectness: []
+
+ function endRounds() {
+ kanaVoicingAnswer.focus = false
+ jLearnerScope.focus = true
+ console.log("Switching back")
+ parent.switchResult(correct/maxRoundNum, "KanaVoicing", pastCorrectness)
+ pastKana = []
+ pastCorrectness = []
+ correct = 0
+ kanaVoicingAnswer.placeholderText = "Type Voicing..."
+ kanaVoicingAnswerBackground.color = kanaVoicingAnswer.activeFocus ? Colors.tertiary_fg : Colors.primary_fg
+ }
+
+ function randomKana() {
+ if (roundNum >= maxRoundNum) {
+ endRounds()
+ }
+ var list = kanaData.kana
+ currentKana = list[Math.floor(Math.random() * list.length)]
+ if (knownList.length > maxRoundNum) {
+ while (pastKana.includes(currentKana)) {
+ currentKana = list[Math.floor(Math.random() * list.length)]
+ }
+ }
+ pastKana.push(currentKana)
+ console.log("Round Num: " + roundNum + " - Choosing Kana: " + currentKana.hira)
+ roundNum = roundNum + 1
+ }
+
+ function randomKnownKana() {
+ if (roundNum >= maxRoundNum) {
+ endRounds()
+ }
+ var knownList = kanaData.kana.filter(k => k.known)
+ currentKana = knownList[Math.floor(Math.random() * knownList.length)]
+ if (knownList.length > maxRoundNum) {
+ while (pastKana.includes(currentKana)) {
+ currentKana = knownList[Math.floor(Math.random() * knownList.length)]
+ }
+ }
+ pastKana.push(currentKana)
+ console.log("Round Num: " + roundNum + " - Choosing Kana: " + currentKana.hira)
+ roundNum = roundNum + 1
+ }
+
+ Column {
+ anchors.fill: parent
+ spacing: 10
+
+ Rectangle {
+ id: kanaVoicingTextRectangale
+ color: Colors.primaryContainer
+ width: kanaVoicingText.width + 20
+ height: parent.height * 0.1
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text {
+ id: kanaVoicingText
+ text: "Im Spiel - Runde: " + kanaVoicing.roundNum
+ anchors.centerIn: parent
+ font.pixelSize: parent.height * 0.6
+ color: Colors.primary
+ }
+ }
+
+ Rectangle {
+ id: kanaVoicingKanaRectangle
+ color: Colors.primaryContainer
+ height: (parent.height - kanaVoicingTextRectangale.height)*0.75
+ width: height
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text {
+ id: kanaVoicingKana
+ text: typeof kanaVoicing.currentKana.hira === "undefined" ? "" : kanaVoicing.currentKana.hira
+ anchors.centerIn: parent
+ font.pixelSize: Math.min(parent.width / text.length * 1.2, parent.height * 0.8)
+ color: Colors.primary
+ }
+ }
+
+ TextField {
+ id: kanaVoicingAnswer
+
+ placeholderText: "Type Voicing..."
+
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ width: parent.width/2
+ height: (parent.height - kanaVoicingTextRectangale.height)*0.25 - parent.spacing*2
+
+ font.pixelSize: height * 0.8
+
+ color: Colors.secondary_fg
+ placeholderTextColor: Colors.secondary
+
+ background: Rectangle {
+ id: kanaVoicingAnswerBackground
+ radius: 10
+ color: kanaVoicingAnswer.activeFocus ? Colors.secondaryContainer : Colors.secondaryContainer_fg
+ border.color: kanaVoicingAnswer.activeFocus ? Colors.secondaryContainer_fg : Colors.secondaryContainer
+ border.width: 1
+ }
+
+ Keys.onReleased: function(event) {
+ if (event.key === Qt.Key_Escape) {
+ console.log("Escape pressed inside TextField")
+ console.log("Pressed Escape, closing JLearner")
+ jLearnerLoader.active = false
+ }
+ }
+
+ onAccepted: function(event) {
+ console.log("Enter pressed inside TextField")
+ if (kanaVoicing.currentKana.voice.includes(kanaVoicingAnswer.text.toLowerCase())){
+ console.log("Correct")
+ kanaVoicingAnswer.text = ""
+ kanaVoicingAnswer.placeholderText = "[" + kanaVoicing.currentKana.voice + "]"
+ kanaVoicingAnswerBackground.color = "green"
+ kanaVoicing.correct = kanaVoicing.correct + 1
+ pastCorrectness.push({
+ kana: kanaVoicing.currentKana.hira,
+ correctness: true,
+ answer: kanaVoicingAnswer.text.toLowerCase(),
+ correct: kanaVoicing.currentKana.voice.join("|")
+ })
+ } else {
+ console.log("Wrong: [" + currentKana.voice + "]")
+ kanaVoicingAnswer.text = ""
+ kanaVoicingAnswerBackground.color = "red"
+ kanaVoicingAnswer.placeholderText = "[" + kanaVoicing.currentKana.voice + "]"
+ pastCorrectness.push({
+ kana: kanaVoicing.currentKana.hira,
+ correctness: false,
+ answer: kanaVoicingAnswer.text.toLowerCase(),
+ correct: kanaVoicing.currentKana.voice.join("|")
+ })
+ }
+ randomKnownKana()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/jl/modules/ModeChooser.qml b/modules/jl/modules/ModeChooser.qml
new file mode 100644
index 0000000..3581b57
--- /dev/null
+++ b/modules/jl/modules/ModeChooser.qml
@@ -0,0 +1,98 @@
+import QtQuick
+import QtQuick.Controls
+import "../../../colors"
+
+Item { // Chooser
+ id: languageChooser
+ width: parent.width
+ height: parent.height
+ anchors.margins: 15
+ visible: false
+
+ Column {
+ anchors.fill: parent
+ spacing: 10
+
+ Rectangle {
+ id: modeChooserUpperText
+ color: Colors.primaryContainer
+ width: mainText.width + 20
+ height: parent.height * 0.1
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text {
+ id: mainText
+ text: "Wähle ein Modus:"
+ anchors.centerIn: parent
+ font.pixelSize: parent.height * 0.6
+ color: Colors.primary
+ }
+ }
+
+ Row {
+ width: parent.width
+ height: parent.height - modeChooserUpperText.height - parent.spacing
+ spacing: 10
+
+ MouseArea {
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ width: (parent.width - parent.spacing) / 2
+ height: parent.height
+
+ onClicked: {
+ console.log("Choosing White Window")
+ languageChooser.parent.switch_KanaVoicing()
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ color: parent.containsMouse ? Colors.secondaryContainer_fg : Colors.secondaryContainer
+
+ radius: 10
+ border.width: 2
+ border.color: parent.containsMouse ? Colors.secondaryContainer : Colors.secondaryContainer_fg
+
+ Text {
+ text: "Kana to Voicing"
+ anchors.centerIn: parent
+ font.pixelSize: Math.min(parent.width / text.length * 1.2, parent.height * 0.8)
+ color: parent.parent.containsMouse ? Colors.secondary_fg : Colors.secondary
+ }
+ }
+ }
+
+ MouseArea {
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ width: (parent.width - parent.spacing) / 2
+ height: parent.height
+
+ onClicked: {
+ console.log("Choosing Black Window")
+ languageChooser.parent.switch_VoicingKana()
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ color: parent.containsMouse ? Colors.secondaryContainer_fg : Colors.secondaryContainer
+
+ radius: 10
+ border.width: 2
+ border.color: parent.containsMouse ? Colors.secondaryContainer : Colors.secondaryContainer_fg
+
+ Text {
+ text: "Voicing to Kana"
+ anchors.centerIn: parent
+ font.pixelSize: Math.min(parent.width / text.length * 1.2, parent.height * 0.8)
+ color: parent.parent.containsMouse ? Colors.secondary_fg : Colors.secondary
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/jl/modules/VoicingKana.qml b/modules/jl/modules/VoicingKana.qml
new file mode 100644
index 0000000..bbdb742
--- /dev/null
+++ b/modules/jl/modules/VoicingKana.qml
@@ -0,0 +1,234 @@
+import QtQuick
+import QtQuick.Controls
+import "../../../colors"
+
+Item { // Kana_Try
+ id: voicingKana
+ width: parent.width
+ height: parent.height
+ anchors.margins: 15
+ visible: false
+
+ // Component.onCompleted: {
+ // randomKnownKana()
+ // }
+
+ function start() {
+ roundNum = 0
+ new_round()
+ }
+
+ KanaData {
+ id: kanaData
+ }
+
+ property int roundNum: 0
+ property int correct: 0
+ property int maxRoundNum: 10
+
+ property var pastKana: []
+ property var pastCorrectness: []
+
+ property var kanaArray: createResults(6)
+ property var targetKana: kanaArray[Math.floor(Math.random() * kanaArray.length)]
+
+ function createResults(n) {
+ var arr = []
+ for (var i = 0; i < n; i++) {
+ arr.push({ hira: "あ", voice: ["a"], known: true })
+ }
+ return arr
+ }
+
+ function new_round() {
+ roundNum = roundNum + 1
+ kanaArray = gemerateKanaArray()
+ targetKana = kanaArray[Math.floor(Math.random() * kanaArray.length)]
+ console.log("Round Num: " + roundNum + " - Choosing Kana: " + targetKana.hira)
+ pastKana.push(targetKana)
+ }
+
+ function gemerateKanaArray() {
+ var arr = []
+ for (var i = 0; i < 6; i++) {
+ arr.push(randomKnownKana(arr))
+ }
+ return arr
+ }
+
+ function randomKana(arr) {
+ if (roundNum > maxRoundNum) {
+ endRounds()
+ }
+ var list = kanaData.kana
+ var newKana = list[Math.floor(Math.random() * list.length)]
+
+ var arrExists = Array.isArray(arr)
+
+ if (knownList.length > maxRoundNum + 6) {
+ if (arrExists) {
+ while (pastKana.includes(newKana) || arr.includes(newKana)) {
+ newKana = list[Math.floor(Math.random() * list.length)]
+ }
+ } else{
+ while (pastKana.includes(newKana)){
+ newKana = list[Math.floor(Math.random() * list.length)]
+ }
+ }
+ } else {
+ if (arrExists) {
+ while (arr.includes(newKana)) {
+ newKana = list[Math.floor(Math.random() * list.length)]
+ }
+ }
+ }
+ kanaArray.push(newKana)
+ return newKana
+ }
+
+ function randomKnownKana(arr) {
+ if (roundNum > maxRoundNum) {
+ endRounds()
+ }
+ var knownList = kanaData.kana.filter(k => k.known)
+ var newKana = knownList[Math.floor(Math.random() * knownList.length)]
+
+ var arrExists = Array.isArray(arr)
+
+ if (knownList.length > maxRoundNum + 6) {
+ if (arrExists) {
+ while (pastKana.includes(newKana) || arr.includes(newKana)) {
+ newKana = knownList[Math.floor(Math.random() * knownList.length)]
+ }
+ } else{
+ while (pastKana.includes(newKana)){
+ newKana = knownList[Math.floor(Math.random() * knownList.length)]
+ }
+ }
+ } else {
+ if (arrExists) {
+ while (arr.includes(newKana)) {
+ newKana = knownList[Math.floor(Math.random() * knownList.length)]
+ }
+ }
+ }
+ return newKana
+ }
+
+ function endRounds() {
+ console.log("Switching back")
+ roundNum = 0
+ parent.switchResult(correct/maxRoundNum, "VoicingKana", pastCorrectness)
+ }
+
+ Column {
+ anchors.fill: parent
+ spacing: 10
+
+ Rectangle {
+ id: voicingKanaTextRectangale
+ color: Colors.primaryContainer
+ width: mainText.width + 20
+ height: parent.height * 0.1
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text {
+ id: mainText
+ text: "Runde: " + roundNum + " - [" + targetKana.voice.join("|") + "]:"
+ anchors.centerIn: parent
+ font.pixelSize: parent.height * 0.6
+ color: Colors.primary
+ }
+ }
+
+ Rectangle{
+ width: parent.width
+ height: parent.height - voicingKanaTextRectangale.height - voicingKanaResultRectangale.height - parent.spacing * 2
+ color: "transparent"
+ Grid {
+ id: grid
+ anchors.fill: parent
+ columns: 3
+ spacing: 10
+
+ property int rows: 2
+
+ // calculate cell width and height based on parent size, spacing, and number of columns/rows
+ property real cellWidth: (width - (columns - 1) * spacing) / columns
+ property real cellHeight: (height - (rows - 1) * spacing) / rows
+
+ Repeater {
+ model: kanaArray
+ MouseArea {
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ width: grid.cellWidth
+ height: grid.cellHeight
+
+ onClicked: {
+ console.log("Choose a Kana: " + modelData.hira)
+ if (modelData.hira === targetKana.hira) {
+ console.log("Correct")
+ correct = correct + 1
+ voicingKanaResultRectangale.color = "green"
+ } else {
+ console.log("Wrong: [" + targetKana.hira + "]")
+ voicingKanaResultRectangale.color = "red"
+ }
+ pastCorrectness.push({
+ kana: targetKana.hira,
+ correctness: modelData.hira === targetKana.hira,
+ answer: modelData.hira,
+ correct: targetKana.voice.join("|")
+ })
+ voicingKanaResult.text = "Letzte Runde: " + targetKana.hira + ": " + "[" + targetKana.voice.join("|") + "]"
+ new_round()
+ }
+
+ Rectangle {
+ width: parent.width
+ height: parent.height
+ color: parent.containsMouse ? Colors.secondaryContainer_fg : Colors.secondaryContainer
+
+ radius: 10
+ border.width: 2
+ border.color: parent.containsMouse ? Colors.secondaryContainer : Colors.secondaryContainer_fg
+
+ Text{
+ text: modelData.hira
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: -parent.height * 0.1
+ font.pixelSize: parent.height * 0.8
+ color: parent.parent.containsMouse ? Colors.secondary_fg : Colors.secondary
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: voicingKanaResultRectangale
+ color: Colors.primaryContainer
+ width: voicingKanaResult.width + 20
+ height: parent.height * 0.1
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ radius: 10
+ border.width: 2
+ border.color: Colors.primaryContainer_fg
+
+ Text {
+ id: voicingKanaResult
+ text: "Wähle das richtige Zeichen"
+ anchors.centerIn: parent
+ font.pixelSize: parent.height * 0.6
+ color: Colors.primary
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/wp/WallpaperPicker.qml b/modules/wp/WallpaperPicker.qml
index a8e3c16..c01b199 100644
--- a/modules/wp/WallpaperPicker.qml
+++ b/modules/wp/WallpaperPicker.qml
@@ -14,6 +14,23 @@ PanelWindow {
visible: true
focusable: true
+ FocusScope {
+ id: pickerScope
+ focus: true
+
+ Keys.onReleased: function(event) {
+ if (event.key === Qt.Key_Escape) {
+ console.log("Pressed Escape, closing Wallpaper Picker")
+ wallpaperLoader.active = false
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ color: "lightblue"
+ }
+ }
+
Rectangle {
anchors.fill: parent
radius: 10
@@ -42,11 +59,22 @@ PanelWindow {
background: Rectangle {
radius: 12
- color: "#2b2b2b"
+ color: filterText.activeFocus ? Colors.tertiary_fg : Colors.primary_fg
border.color: filterText.activeFocus ? Colors.tertiary : Colors.primary
border.width: 1
}
+ Keys.onReleased: {
+ if (event.key === Qt.Key_Escape) {
+ console.log("Escape pressed inside TextField")
+ console.log("Pressed Escape, closing Wallpaper Picker")
+ // wallpaperLoader.active = false
+ filterText.text = ""
+ filterText.focus = false
+ event.accepted = true
+ pickerScope.focus = true
+ }
+ }
}
diff --git a/modules/wp/WallpaperPickerLoader.qml b/modules/wp/WallpaperPickerLoader.qml
index c39f2b0..17a7f41 100644
--- a/modules/wp/WallpaperPickerLoader.qml
+++ b/modules/wp/WallpaperPickerLoader.qml
@@ -28,8 +28,8 @@ Item { // container, invisible
onPressed: {
// generateThumbs.startDetached()
generateThumbs.running = true
- wallpaperLoader.active = !wallpaperLoader.active
console.log("Pressed Meta+Shift+W (Hyprland global shortcut)")
+ wallpaperLoader.active = !wallpaperLoader.active
}
}
diff --git a/modules/wp/generate-wallpaper-thumbs.sh b/modules/wp/generate-wallpaper-thumbs.sh
index 926176f..7322a3d 100755
--- a/modules/wp/generate-wallpaper-thumbs.sh
+++ b/modules/wp/generate-wallpaper-thumbs.sh
@@ -10,7 +10,9 @@ if [ ! -d $THUMB_DIR ]; then
mkdir -p $THUMB_DIR;
fi
-
+shopt -s nullglob
+files=( "$THUMB_DIR"/*.jpg "$THUMB_DIR"/*.jpeg "$THUMB_DIR"/*.png )
+# printf ' %s\n' "${files[@]}"
# Make sure the thumb directory exists
mkdir -p "$THUMB_DIR"
@@ -19,6 +21,28 @@ MAX_DIM=300
num=0
+remove_from_array() {
+ local -n arr=$1
+ local elem=$2
+ local result=()
+
+ # echo "=== Removing element: '$elem' ==="
+ # echo "Original array has ${#arr[@]} elements"
+
+ for item in "${arr[@]}"; do
+ if [[ "$item" != "$elem" ]]; then
+ result+=( "$item" )
+ # echo "Keeping: '$item'"
+ # else
+ # echo "Removing: '$item'"
+ fi
+ done
+
+ arr=( "${result[@]}" )
+ # echo "New array has ${#arr[@]} elements"
+ # echo "-----------------------------------"
+}
+
# Process each image
for img in "$WALLPAPER_DIR"/*.{jpg,jpeg,png}; do
# Skip if no files match
@@ -26,19 +50,44 @@ for img in "$WALLPAPER_DIR"/*.{jpg,jpeg,png}; do
# Get just the filename
fname=$(basename "$img")
+ name="${fname%.*}"
+ ext="${fname##*.}"
+
+ # no magick file:
+ temp_image_name="$THUMB_DIR/${name}_tmp.${ext}"
# Output path
thumb="$THUMB_DIR/$fname"
+
# If thumbnail already exists and is newer than the original, skip
if [ -f "$thumb" ] && [ "$thumb" -nt "$img" ]; then
+ remove_from_array files "$thumb"
continue
fi
num+=1
# Generate thumbnail using ImageMagick
- magick "$img" -resize "${MAX_DIM}x${MAX_DIM}" "$thumb"
+ if [ -x "/usr/bin/magick" ]; then
+ magick "$img" -resize "${MAX_DIM}x${MAX_DIM}" "$thumb"
+ rm $temp_image_name
+ else
+ if [ -f "$temp_image_name" ] && [ "$temp_image_name" -nt "$img" ]; then
+ continue
+ fi
+ cp $img $temp_image_name
+ fi
+ remove_from_array files "$thumb"
+done
+
+# echo ""
+
+# printf ' %s\n' "${files[@]}"
+
+for img in $files; do
+ # echo "$img"
+ rm $img
done
echo "$num Thumbnails generated in $THUMB_DIR"
\ No newline at end of file
diff --git a/modules/wp/set-wallpaper.sh b/modules/wp/set-wallpaper.sh
index f5380b2..c39cb4f 100755
--- a/modules/wp/set-wallpaper.sh
+++ b/modules/wp/set-wallpaper.sh
@@ -42,10 +42,15 @@ elif [ -x "/usr/bin/hyprpaper" ]; then
sleep 0.2
echo "daemon started"
fi
+ sed -i "3s|path = .*|path = ${image}|" ~/.config/hypr/hyprpaper.conf
hyprctl hyprpaper wallpaper ", $image"
fi
-matugen image $image --mode dark
+if [ -x "/usr/bin/matugen" ]; then
+ matugen image $image --mode dark
+else
+ cp $HOME/.config/quickshell/colors/example_colors.qml $HOME/.config/quickshell/colors/Colors.qml
+fi
### Blured Wallpaper for wlogout
blur="50x30"
@@ -61,6 +66,14 @@ echo "$image" > $current_wallpaper
blurred_wp="$static_wallpaper_dir/blurred.jpg"
-magick $image -blur $blur $blurred_wp
+if [ -x "/usr/bin/sddm" ]; then
+ magick $image -blur $blur $blurred_wp
+else
+ cp $image $blurred_wp
+fi
+if [ -x "/usr/bin/sddm" ] && [ -d "/usr/share/backgrounds" ]; then
+ sddm_background="/usr/share/backgrounds/background.jpg"
+ cp "$image" "$sddm_background"
+fi
echo "test"
diff --git a/shell.qml b/shell.qml
index 0b82f33..df318d4 100644
--- a/shell.qml
+++ b/shell.qml
@@ -5,6 +5,7 @@ import Quickshell.Hyprland
import QtQuick //for Text
import "./modules/bar"
import "./modules/wp"
+import "./modules/japanese"
ShellRoot {
id: root
@@ -14,5 +15,8 @@ ShellRoot {
//Wallpaper picker
WallpaperPickerLoader {}
+
+ //Japanese Lerner
+ JLearnerLoader {}
}
\ No newline at end of file