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