Skip to content

Conversation

@dskvr
Copy link

@dskvr dskvr commented Oct 10, 2025

Resolves #123

Summary

This PR adds comprehensive keyboard-driven workspace selection to Hyprexpo, mouse hover feedback, per-monitor workspace configuration, numbered labels with rich styling, configurable inner/outer gaps, native Hyprland-style gradient borders with auto-detection, and rounded tiles with per-state overrides.

Highlights

  • Keyboard Navigation: Arrow key movement, number/letter quick-select, auto-submap integration
  • Mouse Hover: Visual feedback with configurable hover borders and tile rounding
  • Multi-Monitor Support: Per-monitor workspace method configuration via hyprexpo_workspace_method keyword
  • Visual Feedback: Current workspace highlight + separate keyboard focus highlight + mouse hover highlight
  • Rich Labels: Per-tile labels with background bubbles, font controls, pixel-perfect centering
  • Flexible Spacing: gaps_in and gaps_out for clean spacing
  • Auto-Detecting Borders: Unified border_color_* config supports both solid colors (rgb(66ccff)) and gradients (rgba(33ccffee) rgba(00ff99ee) 45deg)
  • Rounded Tiles: Per-state rounding overrides (focus/current/hover) with matching rounded borders

Why

  • Make Hyprexpo fully navigable from keyboard and responsive to mouse, quickly selecting workspaces without mode switching
  • Support multi-monitor setups with different workspace layouts per display
  • Improve visual clarity: show numbers, highlight focus/current/hover states, provide consistent spacing
  • Simplify configuration by auto-detecting border format (solid vs gradient)
  • Align look & feel with Hyprland themes via native gradient borders

Key Features

Keyboard Navigation

  • Dispatchers:
    • hyprexpo:kb_focus <left|right|up|down>: Move keyboard focus
    • hyprexpo:kb_confirm: Select focused tile
    • hyprexpo:kb_selecti <index>: Select by 1-based visual index (recommended; enables 1-key selection)
    • hyprexpo:kb_selectn <id>: Select by workspace ID (legacy; 0→10)
    • hyprexpo:kb_select <token>: Single-character token (1-9, 0, a-z)
  • Auto-entered submap hyprexpo while overview is open (configurable via keynav_enable)
  • Movement modes: Spatial (default) or reading-order horizontal moves
  • Wrap behavior: Configurable per axis (keynav_wrap_h, keynav_wrap_v)

Mouse Hover

  • Real-time hover detection with visual feedback
  • Configurable hover borders (border_color_hover)
  • Per-state tile rounding including hover (tile_rounding_hover)
  • Priority system: focus > current > hover

Multi-Monitor Support

  • Global keyword workspace_method for per-monitor configuration
  • Repeatable syntax: workspace_method = MONITOR_NAME <center|first> <workspace>
  • Backwards compatible: Falls back to plugin:hyprexpo:workspace_method for monitors without specific config
  • Example:
    # Global default (inside plugin block)
    plugin {
        hyprexpo {
            workspace_method = center current
        }
    }
    
    # Per-monitor overrides (outside plugin block, at top level)
    workspace_method = DP-1 first 1
    workspace_method = HDMI-A-1 center 5

Labels (Numbers)

  • Positioning: Centered within label container using ink-bounds for true visual centering
  • Background bubble: circle/square/rounded, custom padding, color, rounding
  • Per-state styling: default/hover/focus/current colors and scale multipliers
  • Font styling: family, bold, italic, underline, strikethrough
  • Pixel snap: On by default to avoid blurry edges
  • Visibility modes: always, hover, focus, hover+focus, current+focus, never
  • Content modes: token (default) | index | id
  • Token overrides: label_token_map up to 50 comma-separated tokens (empty entries skip labels)

Borders (Auto-Detecting)

  • Unified config: border_color_* auto-detects format:
    • Solid: rgb(66ccff) or 0xFF66CCFF
    • Gradient: rgba(33ccffee) rgba(00ff99ee) 45deg
  • Native rendering: Uses CGradientValueData + renderBorder with proper rounded corner support
  • Per-state: Separate configs for current, focus, and hover

Gaps

  • gaps_in: Inner spacing between tiles
  • gaps_out: Outer margin around the grid (animated during open/close)

Config Changes (Breaking / Migration)

Removed/Deprecated

  • gap_sizegaps_in

Border Format

Supports solid and hyprland-style gradient borders (via native api)

border_color_current = rgba(33ccffee) rgba(00ff99ee) 45deg  # Auto-detects gradient
border_color_focus = rgb(ffcc66)                             # Auto-detects solid
border_color_hover = rgb(aabbcc)                             # Auto-detects solid

Minimal Migration Example

plugin {
    hyprexpo {
        # Spacing
        gaps_in = 5
        gaps_out = 0

        # Borders (auto-detecting format)
        border_width = 2
        border_color_current = rgba(33ccffee) rgba(00ff99ee) 45deg  # Gradient
        border_color_focus = rgb(ffcc66)                             # Solid
        border_color_hover = rgb(aabbcc)                             # Solid

        # Tiles (rounded corners with per-state overrides)
        tile_rounding = 12
        tile_rounding_power = 2.0
        tile_rounding_focus = -1      # -1 = inherit from tile_rounding
        tile_rounding_current = -1
        tile_rounding_hover = -1

        # Keyboard navigation
        keynav_enable = 1
        keynav_wrap_h = 1
        keynav_wrap_v = 1

        # Labels (defaults to centered with background bubble)
        label_enable = 1
        label_text_mode = token       # token | index | id
        label_bg_enable = 1
        label_bg_shape = circle
        label_padding = 8
        label_pixel_snap = 1
    }
}

# Per-monitor workspace methods (optional, at top level)
workspace_method = DP-1 first 1
workspace_method = HDMI-A-1 center current

New Options (Grouped)

Keyboard Navigation

  • plugin:hyprexpo:keynav_enable (int/bool): default 1
  • plugin:hyprexpo:keynav_wrap_h, plugin:hyprexpo:keynav_wrap_v: default 1
  • plugin:hyprexpo:keynav_reading_order: default 0 (spatial)

Borders (Auto-Detecting)

  • plugin:hyprexpo:border_width (int): default 2
  • plugin:hyprexpo:border_color (string): default border for unused tiles
  • plugin:hyprexpo:border_color_current (string): default rgb(66ccff)
  • plugin:hyprexpo:border_color_focus (string): default rgb(ffcc66)
  • plugin:hyprexpo:border_color_hover (string): default rgb(aabbcc)

Tile Rounding

  • plugin:hyprexpo:tile_rounding (int): default 0
  • plugin:hyprexpo:tile_rounding_power (float): default 2.0
  • plugin:hyprexpo:tile_rounding_focus (int): default -1 (inherit)
  • plugin:hyprexpo:tile_rounding_current (int): default -1 (inherit)
  • plugin:hyprexpo:tile_rounding_hover (int): default -1 (inherit)

Gaps

  • plugin:hyprexpo:gaps_in (int): default 5
  • plugin:hyprexpo:gaps_out (int): default 0

Labels: Placement and Visibility

  • plugin:hyprexpo:label_enable (int/bool): default 1
  • plugin:hyprexpo:label_position: top-left|top-right|bottom-left|bottom-right|center
  • plugin:hyprexpo:label_offset_x, label_offset_y (int): default 0
  • plugin:hyprexpo:label_show: always|hover|focus|hover+focus|current+focus|never

Labels: Per-State and Font

  • plugin:hyprexpo:label_color_default|hover|focus|current (int colors)
  • plugin:hyprexpo:label_scale_hover, label_scale_focus (float): default 1.0
  • plugin:hyprexpo:label_font_family (string): default Sans
  • plugin:hyprexpo:label_font_size (int): default 16
  • plugin:hyprexpo:label_font_bold, label_font_italic (int): default 0
  • plugin:hyprexpo:label_text_underline, label_text_strikethrough (int): default 0

Labels: Container and Precision

  • plugin:hyprexpo:label_bg_enable (int/bool): default 1
  • plugin:hyprexpo:label_bg_shape: circle|square|rounded
  • plugin:hyprexpo:label_bg_color (int): default 0x88000000
  • plugin:hyprexpo:label_bg_rounding (int): default 8
  • plugin:hyprexpo:label_padding (int): default 8
  • plugin:hyprexpo:label_pixel_snap (int): default 1
  • plugin:hyprexpo:label_center_adjust_x, label_center_adjust_y (int): optical nudge
  • plugin:hyprexpo:label_text_mode: token|index|id
  • plugin:hyprexpo:label_token_map (string): up to 50 comma-separated tokens

Usage (Bindings)

Recommended submap (auto-entered if keynav_enable = 1):

submap = hyprexpo
    # Arrow key navigation
    bind = , left,  hyprexpo:kb_focus, left
    bind = , right, hyprexpo:kb_focus, right
    bind = , up,    hyprexpo:kb_focus, up
    bind = , down,  hyprexpo:kb_focus, down
    bind = , return, hyprexpo:kb_confirm

    # Direct selection via numbers (1-10)
    bind = , 1, hyprexpo:kb_selecti, 1
    bind = , 2, hyprexpo:kb_selecti, 2
    bind = , 3, hyprexpo:kb_selecti, 3
    bind = , 4, hyprexpo:kb_selecti, 4
    bind = , 5, hyprexpo:kb_selecti, 5
    bind = , 6, hyprexpo:kb_selecti, 6
    bind = , 7, hyprexpo:kb_selecti, 7
    bind = , 8, hyprexpo:kb_selecti, 8
    bind = , 9, hyprexpo:kb_selecti, 9
    bind = , 0, hyprexpo:kb_selecti, 10

    # Extended selection via SHIFT+numbers (11-20)
    bind = SHIFT, 1, hyprexpo:kb_selecti, 11
    bind = SHIFT, 2, hyprexpo:kb_selecti, 12
    # ... (continue for 3-9, 0)

    # Alphabetic selection (21-46)
    bind = , a, hyprexpo:kb_selecti, 21
    bind = , b, hyprexpo:kb_selecti, 22
    # ... (continue for c-z)
submap = reset

Testing

# Build
make clean && make

# Load in current session (requires hyprpm or manual plugin loading)
hyprpm reload

# Or add to hyprland.conf:
# exec-once = hyprpm reload -n

Implementation Notes

  • Keyboard nav state: Lives in COverview and integrates with render pass to highlight focus/current/hover
  • Mouse hover: Real-time tracking with damage updates on hover state changes
  • Multi-monitor: Uses global map g_monitorWorkspaceMethods with monitor name keys, falls back to plugin config
  • Labels: Pango+Cairo rasterization to OpenGL textures; centering uses ink bounds for optical alignment with pixel snapping
  • Auto-detecting borders: isGradientBorderSpec() checks for dual rgba() pattern; parses rgb() or hex for solid colors
  • Gradient borders: CGradientValueData + CHyprOpenGLImpl::renderBorder with round/roundingPower for proper rounded corners
  • Outer gap animation: Tied to overview animation to avoid visual jumps

Backward Compatibility

Breaking Changes

  • gap_size renamed to gaps_in
  • outer_gap removed (use gaps_out)
  • border_color_current/focus changed from INT to STRING

Migration Path

# Old config
gap_size = 5
border_style = hyprland
border_color_current = 0xFF66CCFF
border_grad_current = rgba(33ccffee) rgba(00ff99ee) 45deg

# New config
gaps_in = 5
border_color_current = rgba(33ccffee) rgba(00ff99ee) 45deg  # Gradient auto-detected

Deprecated (Still Supported)

  • border_style: Ignored, kept for backwards compatibility
  • border_grad_current/focus/hover: Used as fallback if new border_color_* is empty

Potential Follow-ups

  • Auto-inherit gradients from Hyprland's col.active_border/col.inactive_border when plugin gradient strings not set
  • Multi-digit quick-select with key buffer + timeout
  • Additional label content modes and formatting placeholders
  • Optional shader-based gradients if public API added upstream

Note to maintainers: Feedback welcome on API shape and config naming; happy to adjust for consistency with core. If you prefer not to maintain these features, no worries—I'm already maintaining a fork at hyprexpo-plus.

@dskvr dskvr changed the title [hyprexpo] enhancement: outer gaps, workspace selection, workspace numeration [hyprexpo] enhancement: outer gaps, workspace selection, workspace enumeration Oct 10, 2025
@dskvr dskvr marked this pull request as draft October 10, 2025 16:04
@dskvr dskvr marked this pull request as ready for review October 10, 2025 16:24
@dskvr dskvr changed the title [hyprexpo] enhancement: outer gaps, workspace selection, workspace enumeration [hyprexpo] enhancement: outer gaps, workspace selection, workspace enumeration, tile rounding Oct 10, 2025
@vaxerski
Copy link
Member

this MR will have to wait a moment for #496 to be merged first as it changes the structure a bit

@dskvr dskvr marked this pull request as draft October 12, 2025 12:19
@dskvr
Copy link
Author

dskvr commented Oct 12, 2025

I found an issue related to multi-monitor support anyways, need to refactor some things.

@dskvr dskvr force-pushed the feature/better-hyprexpo branch from 7d55cba to ea8966a Compare October 12, 2025 21:06
@dskvr
Copy link
Author

dskvr commented Oct 12, 2025

minor refactor to support future upstream and rebased feature/better-hyprexpo onto scroll-overview. Not yet fully tested.

Notes:

  1. Current state of this branch "works" with hyprscrolling but is unstable, still needs work.
  2. Attempted to implement as many config values into new overview with mixed success. Was able to get keybinds and tile rounding working, kind of. The issue I encountered was state drift annd unpredictability when using both cursor and keybinds. Faced way too many issues and hit my timebox.
  3. I will wait for scroll-overview to be merged and then take another stab at first making the modifications stable without changing any of the scrolling logic, and only then attempt adding keybinds, multi-monitor support and styling to the nnew scroll-overview files.

@dskvr dskvr changed the title [hyprexpo] enhancement: outer gaps, workspace selection, workspace enumeration, tile rounding [hyprexpo] enhancement: outer gaps, workspace selection, workspace enumeration, tile rounding, multi-monitor Oct 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hyprexpo: keyboard support + a few other thoughts

3 participants