mirror of
https://github.com/hyprwm/hyprland-plugins.git
synced 2026-05-07 11:18:01 +02:00
enhancement: add ws select, numbers & respective config
This commit is contained in:
parent
83dc104773
commit
f9dc9c62ee
6 changed files with 1060 additions and 24 deletions
|
|
@ -5,7 +5,19 @@ else
|
|||
EXTRA_FLAGS =
|
||||
endif
|
||||
|
||||
all:
|
||||
$(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp overview.cpp ExpoGesture.cpp OverviewPassElement.cpp scrollOverview.cpp -o hyprexpo.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -Wno-narrowing
|
||||
CXXFLAGS = -shared -fPIC -g -std=c++2b -Wno-c++11-narrowing -Wno-narrowing
|
||||
INCLUDES = `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon`
|
||||
LIBS = `pkg-config --libs pangocairo`
|
||||
|
||||
SRC = main.cpp overview.cpp ExpoGesture.cpp OverviewPassElement.cpp scrollOverview.cpp
|
||||
TARGET = hyprexpo.so
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SRC)
|
||||
$(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) $(INCLUDES) $^ -o $@ $(LIBS)
|
||||
|
||||
clean:
|
||||
rm ./hyprexpo.so
|
||||
rm -f ./$(TARGET)
|
||||
|
||||
.PHONY: all clean
|
||||
|
|
|
|||
|
|
@ -10,11 +10,54 @@ A great start to configure this plugin would be adding this code to the `plugin`
|
|||
plugin {
|
||||
hyprexpo {
|
||||
columns = 3
|
||||
gap_size = 5
|
||||
gaps_in = 5
|
||||
bg_col = rgb(111111)
|
||||
workspace_method = center current # [center/first] [workspace] e.g. first 1 or center m+1
|
||||
|
||||
gesture_distance = 300 # how far is the "max" for the gesture
|
||||
gaps_out = 0 # outer margin (px)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Keyboard navigation (optional):
|
||||
```ini
|
||||
# Enable keyboard navigation + numbering and borders
|
||||
plugin {
|
||||
hyprexpo {
|
||||
keynav_enable = 1
|
||||
keynav_wrap_h = 1 # wrap horizontally at row edges
|
||||
keynav_wrap_v = 1 # wrap vertically at column edges
|
||||
# set to 1 to enable row-major horizontal moves
|
||||
keynav_reading_order = 0
|
||||
border_style = simple # or hypr (multi-layer) or hyprland (2-color + angle)
|
||||
border_width = 2
|
||||
border_color_current = rgb(66ccff)
|
||||
border_color_focus = rgb(ffcc66)
|
||||
|
||||
gaps_out = 0
|
||||
# numbers (labels)
|
||||
label_enable = 1
|
||||
label_font_size = 16
|
||||
# positioning
|
||||
label_position = top-left # top-left|top-right|bottom-left|bottom-right|center
|
||||
label_offset_x = 6
|
||||
label_offset_y = 6
|
||||
# visibility behavior
|
||||
label_show = always # always|hover|focus|hover+focus|current+focus|never
|
||||
# colors (per state)
|
||||
label_color_default = rgb(ffffff)
|
||||
label_color_hover = rgb(eeeeee)
|
||||
label_color_focus = rgb(ffcc66)
|
||||
label_color_current = rgb(66ccff)
|
||||
# scale multipliers for states
|
||||
label_scale_hover = 1.0
|
||||
label_scale_focus = 1.0
|
||||
# label background bubble
|
||||
label_bg_enable = 0
|
||||
label_bg_color = rgba(00000088)
|
||||
label_bg_rounding = 4
|
||||
label_padding = 4
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -23,21 +66,21 @@ plugin {
|
|||
|
||||
| property | type | description | default |
|
||||
| --- | --- | --- | --- |
|
||||
|columns | number | how many desktops are displayed on one line | `3`|
|
||||
|gap_size | number | gap between desktops | `5`|
|
||||
|bg_col | color | color in gaps (between desktops) | `rgb(000000)`|
|
||||
|workspace_method | [center/first] [workspace] | position of the desktops | `center current`|
|
||||
|skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false`|
|
||||
|gesture_distance | number | how far is the max for the gesture | `300`|
|
||||
| columns | number | how many desktops are displayed on one line | `3` |
|
||||
| gaps_in | number | inner gaps between tiles | `5` |
|
||||
| gaps_out | number | outer margin around the grid | `0` |
|
||||
| bg_col | color | color in gaps (between desktops) | `rgb(000000)` |
|
||||
| workspace_method | [center/first] [workspace] | position of the desktops | `center current` |
|
||||
| skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false` |
|
||||
| gesture_distance | number | how far is the max for the gesture | `300` |
|
||||
|
||||
#### Subcategory `scrolling`
|
||||
|
||||
Applies to the scrolling layout overview
|
||||
| property | type | description | default |
|
||||
| --- | --- | --- | --- |
|
||||
| scroll_moves_up_down | bool | if enabled, scrolling will move workspaces up/down instead of zooming | true |
|
||||
| default_zoom | float | default zoom out value, [0.1 - 0.9] | 0.5 |
|
||||
|
||||
| scroll_moves_up_down | bool | if enabled, scrolling will move workspaces up/down instead of zooming | `true` |
|
||||
| default_zoom | float | default zoom out value, [0.1 - 0.9] | `0.5` |
|
||||
|
||||
### Keywords
|
||||
|
||||
|
|
@ -55,6 +98,26 @@ Example:
|
|||
```bash
|
||||
# This will toggle HyprExpo when SUPER+g is pressed
|
||||
bind = SUPER, g, hyprexpo:expo, toggle
|
||||
|
||||
# Optional submap for keyboard selection when Hyprexpo is active
|
||||
submap = hyprexpo
|
||||
binde = , left, hyprexpo:kb_focus, left
|
||||
binde = , right, hyprexpo:kb_focus, right
|
||||
binde = , up, hyprexpo:kb_focus, up
|
||||
binde = , down, hyprexpo:kb_focus, down
|
||||
binde = , return, hyprexpo:kb_confirm
|
||||
# number selection by workspace id
|
||||
binde = , 1, hyprexpo:kb_selectn, 1
|
||||
binde = , 2, hyprexpo:kb_selectn, 2
|
||||
binde = , 3, hyprexpo:kb_selectn, 3
|
||||
binde = , 4, hyprexpo:kb_selectn, 4
|
||||
binde = , 5, hyprexpo:kb_selectn, 5
|
||||
binde = , 6, hyprexpo:kb_selectn, 6
|
||||
binde = , 7, hyprexpo:kb_selectn, 7
|
||||
binde = , 8, hyprexpo:kb_selectn, 8
|
||||
binde = , 9, hyprexpo:kb_selectn, 9
|
||||
binde = , 0, hyprexpo:kb_selectn, 0 # 0 -> 10
|
||||
submap = reset
|
||||
```
|
||||
|
||||
Here are a list of options you can use:
|
||||
|
|
@ -67,3 +130,27 @@ disable | same as `off`
|
|||
on | displays the overview
|
||||
enable | same as `on`
|
||||
|
||||
Keyboard navigation dispatchers (when overview is active):
|
||||
- `hyprexpo:kb_focus, <left|right|up|down>`: moves focus across tiles (skips invalid).
|
||||
- Wrapping can be configured with `keynav_wrap_h` and `keynav_wrap_v`.
|
||||
- Reading order (row-major) for horizontal movement can be enabled with `keynav_reading_order`. At grid ends it will wrap to start/end only if both `keynav_wrap_h` and `keynav_wrap_v` are enabled.
|
||||
- `hyprexpo:kb_confirm`: selects the focused tile.
|
||||
- `hyprexpo:kb_selectn, <digit>`: selects the workspace with that id if visible in the grid (0 → 10).
|
||||
|
||||
Border styles:
|
||||
- `simple`: single-color border using `border_width`, `border_color_current`, `border_color_focus`.
|
||||
- `hypr`: layered border approximating Hyprland’s gradient borders by drawing 3 layers (lightened, base, and darkened). Uses the same base colors and splits the width across layers.
|
||||
- `hyprland`: 2-color gradient with angle. Provide gradients:
|
||||
- `plugin:hyprexpo:border_grad_current = rgba(33ccffee) rgba(00ff99ee) 45deg`
|
||||
- `plugin:hyprexpo:border_grad_focus = rgba(ffdd44ee) rgba(22aaffee) 30deg`
|
||||
|
||||
Gaps:
|
||||
- `gaps_in` controls the spacing between tiles.
|
||||
- `gaps_out` adds the same spacing around the outer edge of the grid.
|
||||
|
||||
Labels (numbers):
|
||||
- `label_position`, `label_offset_x`, `label_offset_y` control placement per tile.
|
||||
- `label_show` controls when labels are drawn (always/hover/focus/etc.).
|
||||
- Per-state colors: `label_color_default|hover|focus|current`.
|
||||
- Per-state scale multipliers: `label_scale_hover|focus`.
|
||||
- Optional background bubble behind text: `label_bg_*`, `label_padding`.
|
||||
|
|
|
|||
197
hyprexpo/dev-link.sh
Executable file
197
hyprexpo/dev-link.sh
Executable file
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Link local hyprexpo.so into the hyprpm-installed plugin path for quick testing.
|
||||
#
|
||||
# Usage:
|
||||
# ./dev-link.sh # auto-detect installed hyprexpo.so and symlink to local build
|
||||
# ./dev-link.sh -b # build first, then link
|
||||
# ./dev-link.sh -t /path/to/hyprexpo.so # specify target path explicitly
|
||||
# ./dev-link.sh -r # restore original file from .bak and remove symlink
|
||||
#
|
||||
# Notes:
|
||||
# - This script only affects your local user install if hyprpm installed to XDG_DATA_HOME.
|
||||
# - If your hyprexpo is system-wide, you may need sudo to replace it.
|
||||
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local_so="$here/hyprexpo.so"
|
||||
target_so=""
|
||||
do_build=0
|
||||
do_restore=0
|
||||
do_list=0
|
||||
do_interactive=0
|
||||
|
||||
msg() { echo "[dev-link] $*"; }
|
||||
err() { echo "[dev-link] ERROR: $*" >&2; exit 1; }
|
||||
|
||||
usage() {
|
||||
sed -n '2,20p' "$BASH_SOURCE" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
detect_target() {
|
||||
local data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
local cache_home="${XDG_CACHE_HOME:-$HOME/.cache}"
|
||||
local candidates=(
|
||||
"$data_home/hyprpm"
|
||||
"$cache_home/hyprpm"
|
||||
"$data_home/hyprland"
|
||||
"$HOME/.local/lib/hyprland"
|
||||
"/usr/lib/hyprland"
|
||||
"/usr/lib64/hyprland"
|
||||
)
|
||||
|
||||
for base in "${candidates[@]}"; do
|
||||
if [[ -d "$base" ]]; then
|
||||
local found
|
||||
# Prefer plugin tree
|
||||
found=$(find "$base" -type f -name hyprexpo.so 2>/dev/null | head -n1 || true)
|
||||
if [[ -n "${found:-}" ]]; then
|
||||
echo "$found"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
detect_runtime_target() {
|
||||
# Extract hyprexpo.so from the running Hyprland process mappings
|
||||
local pid
|
||||
pid="$(pidof Hyprland 2>/dev/null || pgrep -x Hyprland 2>/dev/null || true)"
|
||||
[[ -n "$pid" ]] || return 1
|
||||
local path
|
||||
# print the last column (pathname) and stop at first match
|
||||
path="$(awk '/hyprexpo\.so/ {print $NF; exit}' "/proc/$pid/maps" 2>/dev/null || true)"
|
||||
if [[ -n "$path" && -f "$path" ]]; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
while (( "$#" )); do
|
||||
case "$1" in
|
||||
-b|--build) do_build=1; shift ;;
|
||||
-t|--target) target_so="${2:-}"; shift 2 ;;
|
||||
-r|--restore) do_restore=1; shift ;;
|
||||
-l|--list) do_list=1; shift ;;
|
||||
-i|--interactive) do_interactive=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) err "Unknown arg: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if (( do_restore )); then
|
||||
if [[ -z "$target_so" ]]; then
|
||||
target_so=$(detect_target || true)
|
||||
fi
|
||||
[[ -n "$target_so" ]] || err "Could not detect hyprexpo.so. Pass -t /path/to/hyprexpo.so"
|
||||
|
||||
if [[ -L "$target_so" ]]; then
|
||||
msg "Removing symlink: $target_so"
|
||||
rm -f -- "$target_so"
|
||||
fi
|
||||
if [[ -f "$target_so.bak" ]]; then
|
||||
msg "Restoring backup: $target_so.bak -> $target_so"
|
||||
mv -f -- "$target_so.bak" "$target_so"
|
||||
else
|
||||
msg "No backup found at $target_so.bak — nothing to restore"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if (( do_build )); then
|
||||
msg "Building hyprexpo.so"
|
||||
make -C "$here" all
|
||||
fi
|
||||
|
||||
[[ -f "$local_so" ]] || err "Local build not found: $local_so (run with -b to build)"
|
||||
|
||||
if [[ -z "$target_so" || $do_list -eq 1 || $do_interactive -eq 1 ]]; then
|
||||
# Prefer the path actually loaded by the running Hyprland instance
|
||||
target_so_runtime="$(detect_runtime_target || true)"
|
||||
# Fallback to filesystem-based detection
|
||||
target_so_fs="$(detect_target || true)"
|
||||
# try hyprpm output as an additional fallback
|
||||
target_so_hpm=""
|
||||
if command -v hyprpm >/dev/null 2>&1; then
|
||||
out="$(hyprpm list 2>/dev/null || true)"
|
||||
if [[ -n "$out" ]]; then
|
||||
target_so_hpm="$(printf '%s\n' "$out" | grep -i hyprexpo | grep -oE '/[^ ]*hyprexpo\.so' | head -n1 || true)"
|
||||
fi
|
||||
fi
|
||||
# last resort: shallow scan
|
||||
target_so_scan="$(find "$HOME/.local/share" "$HOME/.cache" -maxdepth 7 -type f -name hyprexpo.so 2>/dev/null | head -n1 || true)"
|
||||
|
||||
# collect candidates and filter out the local build if present
|
||||
local_abs="$(readlink -f "$local_so")"
|
||||
mapfile -t candidates < <(printf '%s\n' "$target_so_runtime" "$target_so_fs" "$target_so_hpm" "$target_so_scan" | awk 'NF' | awk '!seen[$0]++')
|
||||
filtered=()
|
||||
for c in "${candidates[@]}"; do
|
||||
ca="$(readlink -f "$c" 2>/dev/null || true)"
|
||||
[[ -n "$ca" && "$ca" != "$local_abs" ]] && filtered+=("$ca")
|
||||
done
|
||||
|
||||
if (( do_list )); then
|
||||
if ((${#filtered[@]} == 0)); then
|
||||
msg "No installed hyprexpo.so found (excluding local build)."
|
||||
else
|
||||
printf '%s\n' "${filtered[@]}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if (( do_interactive )); then
|
||||
if ((${#filtered[@]} == 0)); then
|
||||
err "No installed hyprexpo.so found (excluding local build)."
|
||||
fi
|
||||
echo "Select target hyprexpo.so to link:" >&2
|
||||
i=1
|
||||
for c in "${filtered[@]}"; do
|
||||
echo " [$i] $c" >&2
|
||||
i=$((i+1))
|
||||
done
|
||||
read -rp "> " choice
|
||||
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#filtered[@]} )); then
|
||||
err "Invalid selection"
|
||||
fi
|
||||
target_so="${filtered[$((choice-1))]}"
|
||||
else
|
||||
# pick the first filtered candidate if no explicit target provided
|
||||
if [[ -z "$target_so" && ${#filtered[@]} -gt 0 ]]; then
|
||||
target_so="${filtered[0]}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ -n "$target_so" ]] || err "Could not detect hyprexpo.so. Pass -t /path/to/hyprexpo.so"
|
||||
|
||||
msg "Target: $target_so"
|
||||
|
||||
local_abs="$(readlink -f "$local_so")"
|
||||
target_abs="$(readlink -f "$target_so" 2>/dev/null || true)"
|
||||
if [[ -n "$target_abs" && "$target_abs" == "$local_abs" ]]; then
|
||||
msg "Target is the local build; nothing to link."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -L "$target_so" ]]; then
|
||||
current_link="$(readlink -f "$target_so")"
|
||||
if [[ "$current_link" == "$local_abs" ]]; then
|
||||
msg "Already linked to local build. Done."
|
||||
exit 0
|
||||
else
|
||||
msg "Target is a symlink to $current_link — replacing"
|
||||
rm -f -- "$target_so"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$target_so" ]]; then
|
||||
msg "Backing up existing file to $target_so.bak"
|
||||
cp -f -- "$target_so" "$target_so.bak"
|
||||
fi
|
||||
|
||||
msg "Linking $target_so -> $local_so"
|
||||
ln -sf "$local_so" "$target_so"
|
||||
|
||||
msg "Done. Restart Hyprland to load the local build."
|
||||
|
|
@ -14,6 +14,8 @@
|
|||
#include <hyprutils/string/ConstVarList.hpp>
|
||||
using namespace Hyprutils::String;
|
||||
|
||||
#include <cctype>
|
||||
|
||||
#include "globals.hpp"
|
||||
#include "overview.hpp"
|
||||
#include "scrollOverview.hpp"
|
||||
|
|
@ -36,6 +38,11 @@ APICALL EXPORT std::string PLUGIN_API_VERSION() {
|
|||
|
||||
static bool renderingOverview = false;
|
||||
|
||||
// forward declarations for new dispatchers
|
||||
static SDispatchResult onKbFocusDispatcher(std::string arg);
|
||||
static SDispatchResult onKbConfirmDispatcher(std::string arg);
|
||||
static SDispatchResult onKbSelectNumberDispatcher(std::string arg);
|
||||
|
||||
//
|
||||
static void hkRenderWorkspace(void* thisptr, PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, timespec* now, const CBox& geometry) {
|
||||
if (!g_pOverview || renderingOverview || g_pOverview->blockOverviewRendering || g_pOverview->pMonitor != pMonitor)
|
||||
|
|
@ -240,10 +247,15 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:expo", ::onExpoDispatcher);
|
||||
|
||||
// keyboard navigation dispatchers
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:kb_focus", ::onKbFocusDispatcher);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:kb_confirm", ::onKbConfirmDispatcher);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:kb_selectn", ::onKbSelectNumberDispatcher);
|
||||
|
||||
HyprlandAPI::addConfigKeyword(PHANDLE, "hyprexpo-gesture", ::expoGestureKeyword, {});
|
||||
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:columns", Hyprlang::INT{3});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gap_size", Hyprlang::INT{5});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gaps_in", Hyprlang::INT{5});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:bg_col", Hyprlang::INT{0xFF111111});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method", Hyprlang::STRING{"center current"});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty", Hyprlang::INT{0});
|
||||
|
|
@ -252,6 +264,50 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance", Hyprlang::INT{200});
|
||||
|
||||
// keyboard navigation + styling
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:keynav_enable", Hyprlang::INT{1});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:border_style", Hyprlang::STRING{"simple"});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:border_width", Hyprlang::INT{2});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:border_color_current", Hyprlang::INT{0xFF66CCFF});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:border_color_focus", Hyprlang::INT{0xFFFFCC66});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_enable", Hyprlang::INT{1});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_color", Hyprlang::INT{0xFFFFFFFF});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_font_size", Hyprlang::INT{16});
|
||||
// defaults: center/middle within the label container
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_position", Hyprlang::STRING{"center"});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_offset_x", Hyprlang::INT{0});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_offset_y", Hyprlang::INT{0});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_show", Hyprlang::STRING{"always"});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_color_default", Hyprlang::INT{0xFFFFFFFF});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_color_hover", Hyprlang::INT{0xFFEEEEEE});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_color_focus", Hyprlang::INT{0xFFFFCC66});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_color_current", Hyprlang::INT{0xFF66CCFF});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_scale_hover", Hyprlang::FLOAT{1.0f});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_scale_focus", Hyprlang::FLOAT{1.0f});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_bg_enable", Hyprlang::INT{1});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_bg_color", Hyprlang::INT{0x88000000});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_bg_rounding", Hyprlang::INT{8});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_bg_shape", Hyprlang::STRING{"circle"});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_padding", Hyprlang::INT{8});
|
||||
// label font styling and pixel snapping
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_font_family", Hyprlang::STRING{"sans"});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_font_bold", Hyprlang::INT{0});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_font_italic", Hyprlang::INT{0});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_text_underline", Hyprlang::INT{0});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_text_strikethrough", Hyprlang::INT{0});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_pixel_snap", Hyprlang::INT{1});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_center_adjust_x", Hyprlang::INT{0});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_center_adjust_y", Hyprlang::INT{0});
|
||||
// gaps
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gaps_out", Hyprlang::INT{0});
|
||||
// hyprland-style gradient borders per state (string like: "rgba(33ccffee) rgba(00ff99ee) 45deg")
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:border_grad_current", Hyprlang::STRING{""});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:border_grad_focus", Hyprlang::STRING{""});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:keynav_wrap_h", Hyprlang::INT{1});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:keynav_wrap_v", Hyprlang::INT{1});
|
||||
// default off: spatial moves by default
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:keynav_reading_order", Hyprlang::INT{0});
|
||||
|
||||
HyprlandAPI::reloadConfig();
|
||||
|
||||
return {"hyprexpo", "A plugin for an overview", "Vaxry", "1.0"};
|
||||
|
|
@ -264,3 +320,51 @@ APICALL EXPORT void PLUGIN_EXIT() {
|
|||
|
||||
g_pConfigManager->reload(); // we need to reload now to clear all the gestures
|
||||
}
|
||||
|
||||
//
|
||||
// New dispatchers for keyboard navigation
|
||||
//
|
||||
|
||||
static SDispatchResult onKbFocusDispatcher(std::string arg) {
|
||||
if (!g_pOverview)
|
||||
return {};
|
||||
|
||||
if (arg == "left" || arg == "right" || arg == "up" || arg == "down") {
|
||||
g_pOverview->onKbMoveFocus(arg);
|
||||
return {};
|
||||
}
|
||||
|
||||
return {.success = false, .error = "invalid arg. expected left|right|up|down"};
|
||||
}
|
||||
|
||||
static SDispatchResult onKbConfirmDispatcher(std::string arg) {
|
||||
if (!g_pOverview)
|
||||
return {};
|
||||
|
||||
g_pOverview->onKbConfirm();
|
||||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult onKbSelectNumberDispatcher(std::string arg) {
|
||||
if (!g_pOverview)
|
||||
return {};
|
||||
|
||||
// trim spaces
|
||||
while (!arg.empty() && std::isspace(arg.front()))
|
||||
arg.erase(arg.begin());
|
||||
while (!arg.empty() && std::isspace(arg.back()))
|
||||
arg.pop_back();
|
||||
|
||||
if (arg.empty())
|
||||
return {.success = false, .error = "missing number"};
|
||||
|
||||
int num = -1;
|
||||
try {
|
||||
num = std::stoi(arg);
|
||||
} catch (...) {
|
||||
return {.success = false, .error = "invalid number"};
|
||||
}
|
||||
|
||||
g_pOverview->onKbSelectNumber(num);
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,211 @@
|
|||
#include <hyprland/src/helpers/time/Time.hpp>
|
||||
#undef private
|
||||
#include "OverviewPassElement.hpp"
|
||||
#include <hyprland/src/render/OpenGL.hpp>
|
||||
#include <pango/pangocairo.h>
|
||||
#include <cmath>
|
||||
|
||||
struct SHyprGradientSpec {
|
||||
CHyprColor c1;
|
||||
CHyprColor c2;
|
||||
float angleDeg = 0.f;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
static bool parseHexRGBA8(const std::string& s, CHyprColor& out) {
|
||||
// expects 8 hex digits RRGGBBAA
|
||||
if (s.size() != 8)
|
||||
return false;
|
||||
auto hexTo = [](char c) -> int {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
|
||||
if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
|
||||
return -1;
|
||||
};
|
||||
auto byteAt = [&](int i) -> int {
|
||||
int a = hexTo(s[i]);
|
||||
int b = hexTo(s[i+1]);
|
||||
if (a < 0 || b < 0) return -1;
|
||||
return (a << 4) | b;
|
||||
};
|
||||
int r = byteAt(0);
|
||||
int g = byteAt(2);
|
||||
int b = byteAt(4);
|
||||
int a = byteAt(6);
|
||||
if (r < 0 || g < 0 || b < 0 || a < 0) return false;
|
||||
out = CHyprColor{r / 255.f, g / 255.f, b / 255.f, a / 255.f};
|
||||
return true;
|
||||
}
|
||||
|
||||
static SHyprGradientSpec parseGradientSpec(const std::string& inRaw) {
|
||||
// Accept forms like: "rgba(33ccffee) rgba(00ff99ee) 45deg"
|
||||
// Extract 8 hex digits from two rgba(...) groups and an integer angle
|
||||
SHyprGradientSpec spec;
|
||||
std::string s = inRaw;
|
||||
// remove commas
|
||||
s.erase(std::remove(s.begin(), s.end(), ','), s.end());
|
||||
// find 1st rgba(XXXXXXXX)
|
||||
auto p1 = s.find("rgba(");
|
||||
auto p2 = s.find("rgba(", p1 == std::string::npos ? 0 : p1 + 1);
|
||||
if (p1 == std::string::npos || p2 == std::string::npos)
|
||||
return spec;
|
||||
auto e1 = s.find(')', p1);
|
||||
auto e2 = s.find(')', p2);
|
||||
if (e1 == std::string::npos || e2 == std::string::npos)
|
||||
return spec;
|
||||
const std::string hex1 = s.substr(p1 + 5, e1 - (p1 + 5));
|
||||
const std::string hex2 = s.substr(p2 + 5, e2 - (p2 + 5));
|
||||
CHyprColor c1, c2;
|
||||
if (!parseHexRGBA8(hex1, c1) || !parseHexRGBA8(hex2, c2))
|
||||
return spec;
|
||||
// find angle
|
||||
float angle = 0.f;
|
||||
auto pd = s.find("deg", e2);
|
||||
if (pd != std::string::npos) {
|
||||
// collect digits before 'deg'
|
||||
size_t beg = s.rfind(' ', pd);
|
||||
if (beg == std::string::npos)
|
||||
beg = e2 + 1;
|
||||
try {
|
||||
angle = std::stof(s.substr(beg, pd - beg));
|
||||
} catch (...) { angle = 0.f; }
|
||||
}
|
||||
spec.c1 = c1;
|
||||
spec.c2 = c2;
|
||||
spec.angleDeg = angle;
|
||||
spec.valid = true;
|
||||
return spec;
|
||||
}
|
||||
|
||||
static void renderGradientBorder(const CBox& box, int borderSize, const SHyprGradientSpec& grad) {
|
||||
if (!grad.valid || borderSize <= 0)
|
||||
return;
|
||||
|
||||
// gradient direction
|
||||
const float rad = grad.angleDeg * (float)M_PI / 180.f;
|
||||
const Vector2D g{std::cos(rad), std::sin(rad)};
|
||||
// compute min/max dot among corners
|
||||
const Vector2D corners[4] = {{box.x, box.y}, {box.x + box.w, box.y}, {box.x, box.y + box.h}, {box.x + box.w, box.y + box.h}};
|
||||
float minD = 1e9f, maxD = -1e9f;
|
||||
for (auto& c : corners) {
|
||||
float d = c.x * g.x + c.y * g.y;
|
||||
minD = std::min(minD, d);
|
||||
maxD = std::max(maxD, d);
|
||||
}
|
||||
const float range = std::max(1e-3f, maxD - minD);
|
||||
|
||||
auto mixCol = [](const CHyprColor& a, const CHyprColor& b, float t) {
|
||||
t = std::clamp(t, 0.f, 1.f);
|
||||
auto m = CHyprColor{a.r + (b.r - a.r) * t, a.g + (b.g - a.g) * t, a.b + (b.b - a.b) * t, a.a + (b.a - a.a) * t};
|
||||
return m;
|
||||
};
|
||||
|
||||
// choose segment counts
|
||||
const int segW = std::clamp((int)std::round(box.w / 20.0), 8, 64);
|
||||
const int segH = std::clamp((int)std::round(box.h / 20.0), 8, 64);
|
||||
|
||||
auto drawSeg = [&](const CBox& r) {
|
||||
const float cx = r.x + r.w / 2.0;
|
||||
const float cy = r.y + r.h / 2.0;
|
||||
const float d = cx * g.x + cy * g.y;
|
||||
const float t = (d - minD) / range;
|
||||
g_pHyprOpenGL->renderRect(r, mixCol(grad.c1, grad.c2, t), {});
|
||||
};
|
||||
|
||||
// top and bottom bars
|
||||
for (int i = 0; i < segW; ++i) {
|
||||
const double sx = box.x + (double)i * (box.w / segW);
|
||||
const double sw = (i == segW - 1) ? (box.x + box.w - sx) : (box.w / segW);
|
||||
drawSeg(CBox{sx, box.y, sw, (double)borderSize});
|
||||
drawSeg(CBox{sx, box.y + box.h - borderSize, sw, (double)borderSize});
|
||||
}
|
||||
// left and right bars
|
||||
for (int i = 0; i < segH; ++i) {
|
||||
const double sy = box.y + (double)i * (box.h / segH);
|
||||
const double sh = (i == segH - 1) ? (box.y + box.h - sy) : (box.h / segH);
|
||||
drawSeg(CBox{box.x, sy, (double)borderSize, sh});
|
||||
drawSeg(CBox{box.x + box.w - borderSize, sy, (double)borderSize, sh});
|
||||
}
|
||||
}
|
||||
|
||||
static void renderNumberTexture(SP<CTexture> out, const std::string& text, const CHyprColor& color, const Vector2D& bufferSize, const float scale, const int fontSize) {
|
||||
const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bufferSize.x, bufferSize.y);
|
||||
const auto CAIRO = cairo_create(CAIROSURFACE);
|
||||
|
||||
cairo_save(CAIRO);
|
||||
cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);
|
||||
cairo_paint(CAIRO);
|
||||
cairo_restore(CAIRO);
|
||||
|
||||
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
|
||||
pango_layout_set_text(layout, text.c_str(), -1);
|
||||
|
||||
// font options from config
|
||||
static auto* const PFONTFAM = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_font_family")->getDataStaticPtr();
|
||||
static auto* const PFONTB = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_font_bold")->getDataStaticPtr();
|
||||
static auto* const PFONTI = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_font_italic")->getDataStaticPtr();
|
||||
static auto* const PTUNDER = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_text_underline")->getDataStaticPtr();
|
||||
static auto* const PTSTRIKE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_text_strikethrough")->getDataStaticPtr();
|
||||
|
||||
PangoFontDescription* fontDesc = pango_font_description_from_string(*PFONTFAM);
|
||||
pango_font_description_set_size(fontDesc, fontSize * scale * PANGO_SCALE);
|
||||
pango_font_description_set_weight(fontDesc, **PFONTB ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
|
||||
pango_font_description_set_style(fontDesc, **PFONTI ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
|
||||
pango_layout_set_font_description(layout, fontDesc);
|
||||
pango_font_description_free(fontDesc);
|
||||
|
||||
if (**PTUNDER || **PTSTRIKE) {
|
||||
PangoAttrList* attrs = pango_attr_list_new();
|
||||
if (**PTUNDER) {
|
||||
pango_attr_list_insert(attrs, pango_attr_underline_new(PANGO_UNDERLINE_SINGLE));
|
||||
}
|
||||
if (**PTSTRIKE) {
|
||||
pango_attr_list_insert(attrs, pango_attr_strikethrough_new(TRUE));
|
||||
}
|
||||
pango_layout_set_attributes(layout, attrs);
|
||||
pango_attr_list_unref(attrs);
|
||||
}
|
||||
|
||||
pango_layout_set_width(layout, bufferSize.x * PANGO_SCALE);
|
||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
|
||||
|
||||
cairo_set_source_rgba(CAIRO, color.r, color.g, color.b, color.a);
|
||||
|
||||
PangoRectangle ink_rect, logical_rect;
|
||||
pango_layout_get_extents(layout, &ink_rect, &logical_rect);
|
||||
|
||||
// center inside the provided buffer using ink rect (accounts for glyph bearings)
|
||||
const int inkW = std::max(0, ink_rect.width / PANGO_SCALE);
|
||||
const int inkH = std::max(0, ink_rect.height / PANGO_SCALE);
|
||||
const int inkX = ink_rect.x / PANGO_SCALE; // can be negative
|
||||
const int inkY = ink_rect.y / PANGO_SCALE; // can be negative
|
||||
static auto* const* PCENTERADJX = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_center_adjust_x")->getDataStaticPtr();
|
||||
static auto* const* PCENTERADJY = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_center_adjust_y")->getDataStaticPtr();
|
||||
const double xOffset = (bufferSize.x - inkW) / 2.0 - inkX + **PCENTERADJX;
|
||||
const double yOffset = (bufferSize.y - inkH) / 2.0 - inkY + **PCENTERADJY;
|
||||
|
||||
cairo_move_to(CAIRO, xOffset, yOffset);
|
||||
pango_cairo_show_layout(CAIRO, layout);
|
||||
g_object_unref(layout);
|
||||
|
||||
cairo_surface_flush(CAIROSURFACE);
|
||||
|
||||
const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);
|
||||
out->allocate();
|
||||
glBindTexture(GL_TEXTURE_2D, out->m_texID);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
#ifndef GLES2
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
|
||||
#endif
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferSize.x, bufferSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
|
||||
|
||||
cairo_destroy(CAIRO);
|
||||
cairo_surface_destroy(CAIROSURFACE);
|
||||
}
|
||||
|
||||
static void damageMonitor(WP<Hyprutils::Animation::CBaseAnimatedVariable> thisptr) {
|
||||
g_pOverview->damage();
|
||||
|
|
@ -26,6 +231,7 @@ COverview::~COverview() {
|
|||
images.clear(); // otherwise we get a vram leak
|
||||
g_pInputManager->unsetCursorImage();
|
||||
g_pHyprOpenGL->markBlurDirtyForMonitor(pMonitor.lock());
|
||||
resetSubmapIfNeeded();
|
||||
}
|
||||
|
||||
COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn_), swipe(swipe_) {
|
||||
|
|
@ -33,7 +239,7 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn
|
|||
pMonitor = PMONITOR;
|
||||
|
||||
static auto* const* PCOLUMNS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:columns")->getDataStaticPtr();
|
||||
static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gap_size")->getDataStaticPtr();
|
||||
static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gaps_in")->getDataStaticPtr();
|
||||
static auto* const* PCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:bg_col")->getDataStaticPtr();
|
||||
static auto* const* PSKIP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty")->getDataStaticPtr();
|
||||
static auto const* PMETHOD = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method")->getDataStaticPtr();
|
||||
|
|
@ -236,6 +442,8 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn
|
|||
|
||||
mouseButtonHook = g_pHookSystem->hookDynamic("mouseButton", onCursorSelect);
|
||||
touchDownHook = g_pHookSystem->hookDynamic("touchDown", onCursorSelect);
|
||||
|
||||
enterSubmapIfEnabled();
|
||||
}
|
||||
|
||||
void COverview::selectHoveredWorkspace() {
|
||||
|
|
@ -248,6 +456,149 @@ void COverview::selectHoveredWorkspace() {
|
|||
closeOnID = x + y * SIDE_LENGTH;
|
||||
}
|
||||
|
||||
void COverview::ensureKbFocusInitialized() {
|
||||
if (kbFocusID != -1)
|
||||
return;
|
||||
|
||||
// try to set to current openedID
|
||||
if (openedID != -1) {
|
||||
kbFocusID = openedID;
|
||||
return;
|
||||
}
|
||||
|
||||
// fallback: first valid tile
|
||||
for (size_t i = 0; i < images.size(); ++i) {
|
||||
if (isTileValid(i)) {
|
||||
kbFocusID = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool COverview::isTileValid(int id) const {
|
||||
if (id < 0 || id >= SIDE_LENGTH * SIDE_LENGTH)
|
||||
return false;
|
||||
return images[id].workspaceID != WORKSPACE_INVALID;
|
||||
}
|
||||
|
||||
int COverview::tileForWorkspaceID(int wsid) const {
|
||||
for (size_t i = 0; i < images.size(); ++i) {
|
||||
if (images[i].workspaceID == wsid)
|
||||
return (int)i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void COverview::moveFocus(int dx, int dy) {
|
||||
ensureKbFocusInitialized();
|
||||
if (kbFocusID == -1)
|
||||
return;
|
||||
|
||||
int x = kbFocusID % SIDE_LENGTH;
|
||||
int y = kbFocusID / SIDE_LENGTH;
|
||||
|
||||
static auto* const* PWRAPH = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:keynav_wrap_h")->getDataStaticPtr();
|
||||
static auto* const* PWRAPV = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:keynav_wrap_v")->getDataStaticPtr();
|
||||
|
||||
if (dx != 0) {
|
||||
static auto* const* PREADING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:keynav_reading_order")->getDataStaticPtr();
|
||||
int step = dx > 0 ? 1 : -1;
|
||||
if (**PREADING) {
|
||||
// reading-order scan: proceed linearly across the grid (row-major)
|
||||
const int total = SIDE_LENGTH * SIDE_LENGTH;
|
||||
int idx = kbFocusID;
|
||||
for (int tries = 0; tries < total; ++tries) {
|
||||
idx += step;
|
||||
if (idx < 0 || idx >= total) {
|
||||
// wrap only if both wraps are enabled (edge of grid)
|
||||
if (**PWRAPH && **PWRAPV)
|
||||
idx = (idx + total) % total;
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (isTileValid(idx)) {
|
||||
kbFocusID = idx;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// in-row scan with optional horizontal wrap
|
||||
int nx = x;
|
||||
for (int tries = 0; tries < SIDE_LENGTH; ++tries) {
|
||||
nx += step;
|
||||
if (nx < 0 || nx >= SIDE_LENGTH) {
|
||||
if (**PWRAPH)
|
||||
nx = (nx + SIDE_LENGTH) % SIDE_LENGTH;
|
||||
else
|
||||
break;
|
||||
}
|
||||
const int nid = nx + y * SIDE_LENGTH;
|
||||
if (isTileValid(nid)) {
|
||||
kbFocusID = nid;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dy != 0) {
|
||||
int step = dy > 0 ? 1 : -1;
|
||||
int ny = y;
|
||||
for (int tries = 0; tries < SIDE_LENGTH; ++tries) {
|
||||
ny += step;
|
||||
if (ny < 0 || ny >= SIDE_LENGTH) {
|
||||
if (**PWRAPV)
|
||||
ny = (ny + SIDE_LENGTH) % SIDE_LENGTH;
|
||||
else
|
||||
break;
|
||||
}
|
||||
const int nid = x + ny * SIDE_LENGTH;
|
||||
if (isTileValid(nid)) {
|
||||
kbFocusID = nid;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void COverview::onKbMoveFocus(const std::string& dir) {
|
||||
if (closing)
|
||||
return;
|
||||
if (dir == "left")
|
||||
moveFocus(-1, 0);
|
||||
else if (dir == "right")
|
||||
moveFocus(1, 0);
|
||||
else if (dir == "up")
|
||||
moveFocus(0, -1);
|
||||
else if (dir == "down")
|
||||
moveFocus(0, 1);
|
||||
|
||||
damage();
|
||||
}
|
||||
|
||||
void COverview::onKbConfirm() {
|
||||
if (closing)
|
||||
return;
|
||||
ensureKbFocusInitialized();
|
||||
if (kbFocusID != -1)
|
||||
closeOnID = kbFocusID;
|
||||
close();
|
||||
}
|
||||
|
||||
void COverview::onKbSelectNumber(int num) {
|
||||
if (closing)
|
||||
return;
|
||||
|
||||
if (num == 0)
|
||||
num = 10;
|
||||
|
||||
const int tid = tileForWorkspaceID(num);
|
||||
if (tid != -1) {
|
||||
closeOnID = tid;
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
void COverview::redrawID(int id, bool forcelowres) {
|
||||
if (pMonitor->m_activeWorkspace != startedOn && !closing) {
|
||||
// likely user changed.
|
||||
|
|
@ -337,10 +688,13 @@ void COverview::onDamageReported() {
|
|||
Vector2D SIZE = size->value();
|
||||
|
||||
Vector2D tileSize = (SIZE / SIDE_LENGTH);
|
||||
Vector2D tileRenderSize = (SIZE - Vector2D{GAP_WIDTH, GAP_WIDTH} * (SIDE_LENGTH - 1)) / SIDE_LENGTH;
|
||||
const auto GAPSIZE = (closing ? (1.0 - size->getPercent()) : size->getPercent()) * GAP_WIDTH;
|
||||
static auto* const* PGAPSO = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gaps_out")->getDataStaticPtr();
|
||||
const float OUTER = **PGAPSO;
|
||||
Vector2D tileRenderSize = (SIZE - Vector2D{GAPSIZE, GAPSIZE} * (SIDE_LENGTH - 1) - Vector2D{OUTER * 2, OUTER * 2}) / SIDE_LENGTH;
|
||||
// const auto& TILE = images[std::clamp(openedID, 0, SIDE_LENGTH * SIDE_LENGTH)];
|
||||
CBox texbox = CBox{(openedID % SIDE_LENGTH) * tileRenderSize.x + (openedID % SIDE_LENGTH) * GAP_WIDTH,
|
||||
(openedID / SIDE_LENGTH) * tileRenderSize.y + (openedID / SIDE_LENGTH) * GAP_WIDTH, tileRenderSize.x, tileRenderSize.y}
|
||||
CBox texbox = CBox{OUTER + (openedID % SIDE_LENGTH) * tileRenderSize.x + (openedID % SIDE_LENGTH) * GAPSIZE,
|
||||
OUTER + (openedID / SIDE_LENGTH) * tileRenderSize.y + (openedID / SIDE_LENGTH) * GAPSIZE, tileRenderSize.x, tileRenderSize.y}
|
||||
.translate(pMonitor->m_position);
|
||||
|
||||
damage();
|
||||
|
|
@ -355,6 +709,8 @@ void COverview::close() {
|
|||
if (closing)
|
||||
return;
|
||||
|
||||
resetSubmapIfNeeded();
|
||||
|
||||
const int ID = closeOnID == -1 ? openedID : closeOnID;
|
||||
|
||||
const auto& TILE = images[std::clamp(ID, 0, SIDE_LENGTH * SIDE_LENGTH)];
|
||||
|
|
@ -433,20 +789,259 @@ void COverview::fullRender() {
|
|||
|
||||
Vector2D SIZE = size->value();
|
||||
|
||||
static auto* const* PGAPSO = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gaps_out")->getDataStaticPtr();
|
||||
const float OUTER = **PGAPSO;
|
||||
|
||||
Vector2D tileSize = (SIZE / SIDE_LENGTH);
|
||||
Vector2D tileRenderSize = (SIZE - Vector2D{GAPSIZE, GAPSIZE} * (SIDE_LENGTH - 1)) / SIDE_LENGTH;
|
||||
Vector2D tileRenderSize = (SIZE - Vector2D{GAPSIZE, GAPSIZE} * (SIDE_LENGTH - 1) - Vector2D{OUTER * 2, OUTER * 2}) / SIDE_LENGTH;
|
||||
|
||||
g_pHyprOpenGL->clear(BG_COLOR.stripA());
|
||||
|
||||
for (size_t y = 0; y < (size_t)SIDE_LENGTH; ++y) {
|
||||
for (size_t x = 0; x < (size_t)SIDE_LENGTH; ++x) {
|
||||
CBox texbox = {x * tileRenderSize.x + x * GAPSIZE, y * tileRenderSize.y + y * GAPSIZE, tileRenderSize.x, tileRenderSize.y};
|
||||
const int id = x + y * SIDE_LENGTH;
|
||||
CBox texbox{OUTER + x * tileRenderSize.x + x * GAPSIZE, OUTER + y * tileRenderSize.y + y * GAPSIZE, tileRenderSize.x, tileRenderSize.y};
|
||||
texbox.scale(pMonitor->m_scale).translate(pos->value());
|
||||
texbox.round();
|
||||
CRegion damage{0, 0, INT16_MAX, INT16_MAX};
|
||||
g_pHyprOpenGL->renderTextureInternal(images[x + y * SIDE_LENGTH].fb.getTexture(), texbox, {.damage = &damage, .a = 1.0});
|
||||
g_pHyprOpenGL->renderTextureInternal(images[id].fb.getTexture(), texbox, {.damage = &damage, .a = 1.0});
|
||||
}
|
||||
}
|
||||
|
||||
// overlays: numbers and borders
|
||||
static auto* const* PLABELEN = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_enable")->getDataStaticPtr();
|
||||
static auto* const* PLABELCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_color")->getDataStaticPtr();
|
||||
static auto* const* PLABELSIZE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_font_size")->getDataStaticPtr();
|
||||
static auto const* PLABELPOS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_position")->getDataStaticPtr();
|
||||
static auto* const* PLABELOX = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_offset_x")->getDataStaticPtr();
|
||||
static auto* const* PLABELOY = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_offset_y")->getDataStaticPtr();
|
||||
static auto const* PLABELSHOW = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_show")->getDataStaticPtr();
|
||||
static auto* const* PLCOLDEF = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_color_default")->getDataStaticPtr();
|
||||
static auto* const* PLCOLHOV = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_color_hover")->getDataStaticPtr();
|
||||
static auto* const* PLCOLFOC = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_color_focus")->getDataStaticPtr();
|
||||
static auto* const* PLCOLCUR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_color_current")->getDataStaticPtr();
|
||||
static auto* const* PLSCALEH = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_scale_hover")->getDataStaticPtr();
|
||||
static auto* const* PLSCALEF = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_scale_focus")->getDataStaticPtr();
|
||||
static auto* const* PLBGEN = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_bg_enable")->getDataStaticPtr();
|
||||
static auto* const* PLBGCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_bg_color")->getDataStaticPtr();
|
||||
static auto* const* PLBGROUND = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_bg_rounding")->getDataStaticPtr();
|
||||
static auto const* PLBGSHAPE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_bg_shape")->getDataStaticPtr();
|
||||
static auto* const* PLBGPAD = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_padding")->getDataStaticPtr();
|
||||
|
||||
static auto* const* PBWIDTH = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:border_width")->getDataStaticPtr();
|
||||
static auto* const* PBCURCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:border_color_current")->getDataStaticPtr();
|
||||
static auto* const* PBFOCCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:border_color_focus")->getDataStaticPtr();
|
||||
static auto const* PBSTYLE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:border_style")->getDataStaticPtr();
|
||||
static auto const* PBGRCUR = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:border_grad_current")->getDataStaticPtr();
|
||||
static auto const* PBGREFOC = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:border_grad_focus")->getDataStaticPtr();
|
||||
|
||||
// draw labels
|
||||
if (**PLABELEN) {
|
||||
// hovered tile (approximate like selectHoveredWorkspace)
|
||||
int hoveredID = -1;
|
||||
if (!closing) {
|
||||
int hx = std::clamp((int)(lastMousePosLocal.x / pMonitor->m_size.x * SIDE_LENGTH), 0, SIDE_LENGTH - 1);
|
||||
int hy = std::clamp((int)(lastMousePosLocal.y / pMonitor->m_size.y * SIDE_LENGTH), 0, SIDE_LENGTH - 1);
|
||||
hoveredID = hx + hy * SIDE_LENGTH;
|
||||
}
|
||||
|
||||
auto shouldShow = [&](int id) -> bool {
|
||||
if (std::string{*PLABELSHOW} == "never")
|
||||
return false;
|
||||
if (std::string{*PLABELSHOW} == "always")
|
||||
return true;
|
||||
const bool isHover = id == hoveredID;
|
||||
const bool isFocus = id == kbFocusID;
|
||||
const bool isCurr = id == openedID;
|
||||
const std::string mode{*PLABELSHOW};
|
||||
if (mode == "hover")
|
||||
return isHover;
|
||||
if (mode == "focus")
|
||||
return isFocus;
|
||||
if (mode == "hover+focus")
|
||||
return isHover || isFocus;
|
||||
if (mode == "current+focus")
|
||||
return isCurr || isFocus;
|
||||
return true;
|
||||
};
|
||||
|
||||
auto resolveState = [&](int id) -> int {
|
||||
// precedence: focus > current > hover > default
|
||||
if (id == kbFocusID)
|
||||
return 2; // focus
|
||||
if (id == openedID)
|
||||
return 3; // current
|
||||
if (id == hoveredID)
|
||||
return 1; // hover
|
||||
return 0; // default
|
||||
};
|
||||
|
||||
auto placeBox = [&](const CBox& tile, const Vector2D& size) -> CBox {
|
||||
double x = tile.x, y = tile.y;
|
||||
const std::string pos{*PLABELPOS};
|
||||
if (pos == "top-left") {
|
||||
x += **PLABELOX; y += **PLABELOY;
|
||||
} else if (pos == "top-right") {
|
||||
x += tile.w - size.x - **PLABELOX; y += **PLABELOY;
|
||||
} else if (pos == "bottom-left") {
|
||||
x += **PLABELOX; y += tile.h - size.y - **PLABELOY;
|
||||
} else if (pos == "bottom-right") {
|
||||
x += tile.w - size.x - **PLABELOX; y += tile.h - size.y - **PLABELOY;
|
||||
} else { // center
|
||||
x += (tile.w - size.x) / 2.0; y += (tile.h - size.y) / 2.0;
|
||||
}
|
||||
return CBox{x, y, (double)size.x, (double)size.y};
|
||||
};
|
||||
|
||||
for (size_t y = 0; y < (size_t)SIDE_LENGTH; ++y) {
|
||||
for (size_t x = 0; x < (size_t)SIDE_LENGTH; ++x) {
|
||||
const int id = x + y * SIDE_LENGTH;
|
||||
if (images[id].workspaceID == WORKSPACE_INVALID || !shouldShow(id))
|
||||
continue;
|
||||
|
||||
// compute tile box again for label placement
|
||||
CBox tile{OUTER + x * tileRenderSize.x + x * GAPSIZE, OUTER + y * tileRenderSize.y + y * GAPSIZE, tileRenderSize.x, tileRenderSize.y};
|
||||
tile.scale(pMonitor->m_scale).translate(pos->value());
|
||||
tile.round();
|
||||
|
||||
const std::string label = std::to_string(images[id].workspaceID);
|
||||
const int baseF = std::max(8, (int)**PLABELSIZE);
|
||||
const int st = resolveState(id);
|
||||
|
||||
auto ensureTex = [&](SP<CTexture>& tex, Vector2D& sz, const CHyprColor& col, float scaleMul) {
|
||||
if (!tex)
|
||||
tex = makeShared<CTexture>();
|
||||
if (tex->m_texID == 0) {
|
||||
const int fsz = std::max(8, (int)std::round(baseF * scaleMul));
|
||||
Vector2D buf{std::max(32, fsz * 2), std::max(24, fsz + 8)};
|
||||
sz = buf;
|
||||
renderNumberTexture(tex, label, col, buf, pMonitor->m_scale, fsz);
|
||||
}
|
||||
};
|
||||
|
||||
CHyprColor col = CHyprColor{(uint64_t)**PLCOLDEF};
|
||||
float scl = 1.0f;
|
||||
static auto* const* PLPIXELSNAP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_pixel_snap")->getDataStaticPtr();
|
||||
|
||||
auto drawWithBG = [&](SP<CTexture>& tex, const Vector2D& tsize) {
|
||||
const int pad = **PLBGPAD;
|
||||
// background size
|
||||
Vector2D bgSize = {tsize.x + pad * 2, tsize.y + pad * 2};
|
||||
const std::string shape{*PLBGSHAPE};
|
||||
int roundPx = **PLBGROUND;
|
||||
if (shape == "circle" || shape == "square") {
|
||||
const double side = std::max(bgSize.x, bgSize.y);
|
||||
bgSize = {side, side};
|
||||
roundPx = (shape == "circle") ? std::lround(side / 2.0) : 0;
|
||||
}
|
||||
CBox bg = placeBox(tile, bgSize);
|
||||
// center text within bg
|
||||
CBox lb{bg.x + (bg.w - tsize.x) / 2.0, bg.y + (bg.h - tsize.y) / 2.0, (double)tsize.x, (double)tsize.y};
|
||||
if (**PLPIXELSNAP) {
|
||||
bg.round();
|
||||
lb.round();
|
||||
}
|
||||
// draw
|
||||
g_pHyprOpenGL->renderRect(bg, CHyprColor{(uint64_t)**PLBGCOL}, {.round = roundPx});
|
||||
g_pHyprOpenGL->renderTexture(tex, lb, {.a = 1.0});
|
||||
};
|
||||
|
||||
auto drawNoBG = [&](SP<CTexture>& tex, const Vector2D& tsize) {
|
||||
CBox lb = placeBox(tile, tsize);
|
||||
if (**PLPIXELSNAP)
|
||||
lb.round();
|
||||
g_pHyprOpenGL->renderTexture(tex, lb, {.a = 1.0});
|
||||
};
|
||||
|
||||
if (st == 1) { // hover
|
||||
col = CHyprColor{(uint64_t)**PLCOLHOV};
|
||||
scl = **PLSCALEH;
|
||||
ensureTex(images[id].labelTexHover, images[id].labelSizeHover, col, scl);
|
||||
if (**PLBGEN)
|
||||
drawWithBG(images[id].labelTexHover, images[id].labelSizeHover);
|
||||
else
|
||||
drawNoBG(images[id].labelTexHover, images[id].labelSizeHover);
|
||||
} else if (st == 2) { // focus
|
||||
col = CHyprColor{(uint64_t)**PLCOLFOC};
|
||||
scl = **PLSCALEF;
|
||||
ensureTex(images[id].labelTexFocus, images[id].labelSizeFocus, col, scl);
|
||||
if (**PLBGEN)
|
||||
drawWithBG(images[id].labelTexFocus, images[id].labelSizeFocus);
|
||||
else
|
||||
drawNoBG(images[id].labelTexFocus, images[id].labelSizeFocus);
|
||||
} else if (st == 3) { // current
|
||||
col = CHyprColor{(uint64_t)**PLCOLCUR};
|
||||
ensureTex(images[id].labelTexCurrent, images[id].labelSizeCurrent, col, 1.0f);
|
||||
if (**PLBGEN)
|
||||
drawWithBG(images[id].labelTexCurrent, images[id].labelSizeCurrent);
|
||||
else
|
||||
drawNoBG(images[id].labelTexCurrent, images[id].labelSizeCurrent);
|
||||
} else { // default
|
||||
ensureTex(images[id].labelTexDefault, images[id].labelSizeDefault, CHyprColor{(uint64_t)**PLCOLDEF}, 1.0f);
|
||||
if (**PLBGEN)
|
||||
drawWithBG(images[id].labelTexDefault, images[id].labelSizeDefault);
|
||||
else
|
||||
drawNoBG(images[id].labelTexDefault, images[id].labelSizeDefault);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw borders for current and focus
|
||||
auto drawBorderForID = [&](int id, bool isFocus, const CHyprColor& colFallback) {
|
||||
if (id < 0)
|
||||
return;
|
||||
const int ix = id % SIDE_LENGTH;
|
||||
const int iy = id / SIDE_LENGTH;
|
||||
CBox box{OUTER + ix * tileRenderSize.x + ix * GAPSIZE, OUTER + iy * tileRenderSize.y + iy * GAPSIZE, tileRenderSize.x, tileRenderSize.y};
|
||||
box.scale(pMonitor->m_scale).translate(pos->value());
|
||||
box.round();
|
||||
const int BWIDTH = std::max(1, (int)**PBWIDTH);
|
||||
|
||||
const std::string style{*PBSTYLE};
|
||||
if (style == "hyprland") {
|
||||
const std::string specStr = isFocus ? std::string{*PBGREFOC} : std::string{*PBGRCUR};
|
||||
const auto spec = parseGradientSpec(specStr);
|
||||
if (spec.valid)
|
||||
renderGradientBorder(box, BWIDTH, spec);
|
||||
else
|
||||
g_pHyprOpenGL->renderBorder(box, colFallback, {.borderSize = BWIDTH});
|
||||
} else if (style == "hypr") {
|
||||
auto tint = [](const CHyprColor& c, float f) -> CHyprColor {
|
||||
auto clamp01 = [](float v) { return std::clamp(v, 0.0f, 1.0f); };
|
||||
CHyprColor r;
|
||||
r.r = clamp01(c.r + f);
|
||||
r.g = clamp01(c.g + f);
|
||||
r.b = clamp01(c.b + f);
|
||||
r.a = c.a;
|
||||
return r;
|
||||
};
|
||||
const int l0 = std::max(1, BWIDTH / 3);
|
||||
const int l1 = std::max(1, BWIDTH / 3);
|
||||
const int l2 = std::max(1, BWIDTH - l0 - l1);
|
||||
const int layers[3] = {l0, l1, l2};
|
||||
const CHyprColor cols[3] = {tint(colFallback, 0.12f), colFallback, tint(colFallback, -0.12f)};
|
||||
|
||||
CBox b = box;
|
||||
int prev = 0;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (i != 0) {
|
||||
b.x -= prev;
|
||||
b.y -= prev;
|
||||
b.width += prev * 2;
|
||||
b.height += prev * 2;
|
||||
}
|
||||
g_pHyprOpenGL->renderBorder(b, cols[i], {.borderSize = layers[i]});
|
||||
prev += layers[i];
|
||||
}
|
||||
} else {
|
||||
g_pHyprOpenGL->renderBorder(box, colFallback, {.borderSize = BWIDTH});
|
||||
}
|
||||
};
|
||||
|
||||
drawBorderForID(openedID, false, CHyprColor{(uint64_t)**PBCURCOL});
|
||||
if (kbFocusID != -1)
|
||||
drawBorderForID(kbFocusID, true, CHyprColor{(uint64_t)**PBFOCCOL});
|
||||
}
|
||||
|
||||
static float lerp(const float& from, const float& to, const float perc) {
|
||||
|
|
@ -505,3 +1100,19 @@ void COverview::onSwipeEnd() {
|
|||
swipeWasCommenced = true;
|
||||
m_isSwiping = false;
|
||||
}
|
||||
|
||||
void COverview::enterSubmapIfEnabled() {
|
||||
static auto* const* PKEYNAV = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:keynav_enable")->getDataStaticPtr();
|
||||
if (**PKEYNAV && !submapActive) {
|
||||
// switch to a dedicated submap for hyprexpo navigation
|
||||
g_pKeybindManager->m_dispatchers["submap"]("hyprexpo");
|
||||
submapActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
void COverview::resetSubmapIfNeeded() {
|
||||
if (submapActive) {
|
||||
g_pKeybindManager->m_dispatchers["submap"]("reset");
|
||||
submapActive = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "globals.hpp"
|
||||
#include <hyprland/src/desktop/DesktopTypes.hpp>
|
||||
#include <hyprland/src/render/Framebuffer.hpp>
|
||||
#include <hyprland/src/render/Texture.hpp>
|
||||
#include <hyprland/src/helpers/AnimatedVariable.hpp>
|
||||
#include <hyprland/src/managers/HookSystemManager.hpp>
|
||||
#include <vector>
|
||||
|
|
@ -34,15 +35,28 @@ class COverview : public IOverview {
|
|||
virtual void onSwipeEnd();
|
||||
|
||||
// close without a selection
|
||||
virtual void close();
|
||||
virtual void selectHoveredWorkspace();
|
||||
virtual void close() override;
|
||||
virtual void selectHoveredWorkspace() override;
|
||||
|
||||
virtual void fullRender();
|
||||
// keyboard navigation interface
|
||||
virtual void onKbMoveFocus(const std::string& dir) override;
|
||||
virtual void onKbConfirm() override;
|
||||
virtual void onKbSelectNumber(int num) override;
|
||||
virtual void onKbSelectToken(int visibleIdx) override;
|
||||
|
||||
virtual void fullRender() override;
|
||||
|
||||
private:
|
||||
void redrawID(int id, bool forcelowres = false);
|
||||
void redrawAll(bool forcelowres = false);
|
||||
void onWorkspaceChange();
|
||||
void ensureKbFocusInitialized();
|
||||
bool isTileValid(int id) const;
|
||||
void moveFocus(int dx, int dy);
|
||||
int tileForWorkspaceID(int wsid) const;
|
||||
int tileForVisibleIndex(int vIdx) const;
|
||||
void enterSubmapIfEnabled();
|
||||
void resetSubmapIfNeeded();
|
||||
|
||||
int SIDE_LENGTH = 3;
|
||||
int GAP_WIDTH = 5;
|
||||
|
|
@ -55,12 +69,23 @@ class COverview : public IOverview {
|
|||
int64_t workspaceID = -1;
|
||||
PHLWORKSPACE pWorkspace;
|
||||
CBox box;
|
||||
// Label textures per state for customization
|
||||
SP<CTexture> labelTexDefault;
|
||||
SP<CTexture> labelTexHover;
|
||||
SP<CTexture> labelTexFocus;
|
||||
SP<CTexture> labelTexCurrent;
|
||||
Vector2D labelSizeDefault = {0, 0};
|
||||
Vector2D labelSizeHover = {0, 0};
|
||||
Vector2D labelSizeFocus = {0, 0};
|
||||
Vector2D labelSizeCurrent = {0, 0};
|
||||
};
|
||||
|
||||
Vector2D lastMousePosLocal = Vector2D{};
|
||||
|
||||
int openedID = -1;
|
||||
int closeOnID = -1;
|
||||
int kbFocusID = -1;
|
||||
bool submapActive = false;
|
||||
|
||||
std::vector<SWorkspaceImage> images;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue