feature: icon layer#26
Merged
Merged
Conversation
Adds a grid of application shortcut icons on the desktop background (above
the wallpaper, below the status/task bars) so apps launch directly from the
desktop — parity with CCIMXDesktop. The merged app list (builtin panels +
manifest apps + XDG .desktop) is reused as-is.
Phase 1 — DesktopIconLayer (ui/components/desktop_icon_layer/):
- Plain child QWidget of the desktop surface: NOT an IShellLayer (wallpaper-
strategy specific) and NOT an IPanel (edge-anchored). Consumes
PanelManager::availableGeometry() like the launcher popup.
- Constructed between the wallpaper shell layer and the bars so Qt child
z-order stacks it correctly with no z-order API changes.
- Transparent + WA_TransparentForMouseEvents container: empty cells pass
clicks through to the wallpaper; LauncherTile children keep their input.
- computeGridDimensions() pure helper (cell/row math, cap-and-truncate with
a logged warning on overflow). 10 unit tests.
- Reuses LauncherTile and the shared launch_app path, so a desktop launch
lights the taskbar running indicator like any other entry point.
Phase 2 — real app icons (closes a project-wide gap):
- app_icon_resolver.{h,cpp}: resolves AppEntry::icon_path via absolute path
/ Qt resource / QIcon::fromTheme, null on miss (letter fallback). System
.desktop theme-name icons (firefox etc.) now render instead of failing.
- LauncherTile caches the resolved icon at 2x for crisp hover and draws it
when present, else the existing initial-letter fallback. Benefits the
launcher popup, taskbar, and desktop layer uniformly. 4 unit tests.
Verified: fast develop build green; ctest full suite passing (incl. 10 grid
+ 4 resolver tests); doxygen lint clean.
DesktopIconLayer: drop WA_TransparentForMouseEvents — it starves child widgets of mouse events on several Qt versions (and on WSLg), so desktop tiles never received clicks. The attribute was only there to pass empty-cell clicks through to the wallpaper, which has no click handler today, so it is net-negative. Empty-cell clicks now fall through via QWidget's default ignore. TaskbarIcon: resolve full-color icons for system .desktop entries (theme names like "firefox") and manifest apps via resolve_app_icon, instead of only tinting builtin ":/..." masks. Builtin taskbar resources stay single-color (tinted to on-surface); everything else shows real artwork. Mirrors what LauncherTile and DesktopIconLayer already do. Verified: build green, ctest full suite passing, doxygen clean.
Turns the desktop icon layer from a read-only full app list into a
user-managed shortcut set: each shortcut = {app_id, col, row}, persisted
across sessions via ConfigStore (domain "desktop_icons", JSON string at
the User layer), seeded with a default set on first run.
Touch-first (i.MX6ULL resistive, no mouse):
- LauncherTile gains TileContext + a long-press state machine (500ms hold,
10px move-cancel threshold). Desktop context: long-press enters drag;
Launcher context: long-press emits "send to desktop". A quick tap still
launches (click path intact). Two-threshold rule: <10px wander is still a
tap, >=10px before the timer scraps (no click, no drag).
- Drag uses a floating ghost (tile->grab() pixmap) over a dimmed source
tile so QGridLayout never reflows mid-drag (critical on a 528MHz A7).
- Drop on a grid cell swaps positions; drop outside the layer removes the
shortcut. A snap-highlight rect is painted on the target cell.
Data + persistence:
- DesktopShortcut {app_id, col, row} + DesktopShortcutStore (load / save /
seedFrom via ConfigStore; serialize/deserialize JSON round-trip exposed
for unit tests, which cover round-trip, malformed input, empty-app_id
filtering, and seed layout/wrap/cap).
- DesktopIconLayer switches setApps -> setShortcuts(shortcuts, registry);
emits shortcutsChanged on any layout change so the caller persists.
- CFDesktopEntity loads (seeds+saves on first run) and wires
shortcutsChanged -> DesktopShortcutStore::save, plus
app_launcher::addToDesktopRequested -> icon_layer::addShortcut.
Verified: build green, ctest full suite passing (grid math + shortcut
store JSON + seed), doxygen clean.
Four root causes behind the desktop icon "bounces back" / "flies on resize" bug, all in rebuildGrid / cellAt: - singleShot(0) rebuildGrid: rebuildGrid ran inside the drag-source tile's mouseReleaseEvent handler; deleting the event-receiving widget mid-event is Qt's deferred-delete trap (data updated, screen never refreshed). - removeWidget before delete: QGridLayout keeps a stale item until the destroyed signal fires asynchronously; new addWidget then landed at (0,0). - setRow/ColumnMinimumHeight/Width: empty rows/cols collapsed to 0, so a tile at row 5 with rows 2-4 empty rendered visually at row 2 and cellAt()'s (margin + row*stride) math no longer matched the widget pos. - clear stale minimums before re-setting: shrinking the window otherwise left the old (larger) grid's minimums in place and flung tiles around. Drops the temporary geom / dragMove / widget.pos diagnostics now that the root causes are fixed; the dragEnd + swap/move logs stay as traces.
The desktop icon grid left wide displays half-empty due to two caps: - computeGridDimensions capped columns at kMaxColumns=8 even when the available width fit 14+; dropped the cap so the grid fills the width. - seedFrom hardcoded kSeedCols=8; now takes cols from the caller, and CFDesktopEntity derives it from the primary screen width so first-run layout fills the screen instead of clustering in the left half. Updates grid-math unit tests for the uncapped column count.
…metry cellAt and paintEvent computed the snap cell by hand as margin + row*stride, which drifted off the actual cell positions QGridLayout picks once its alignment / minimum-size / spacing decisions move them — the highlight ended up offset from the tile that would land there. Both now use grid_->cellRect(row, col): cellAt walks the real cell rects to find the one under the pointer (snapping to the nearest cell inside the margin band instead of misclassifying it as a delete), and paintEvent draws the highlight at the real cell rect. Drag preview and drop target now line up exactly with the tiles.
DesktopIconLayer tiles were fixed at 96px; when the window shrank, tiles beyond the grid capacity vanished (skipped). Now computeGridDimensions scans cell from 96 down to 48 and picks the largest edge whose cols*rows still fits app_count, shrinking tiles proportionally so all shortcuts stay visible. Only when app_count exceeds even the 48px capacity does it truncate. - GridDimensions gains a cell field ([48,96]; 0 when no valid layout). - LauncherTile.paintEvent derives all geometry/fonts from width()/96 so it renders correctly at any scaled cell; launcher popup tiles keep the 96px constructor default and reproduce the original pixel layout exactly (CONST/96 form avoids float drift, e.g. label size stays 13). - applyTheme no longer sets font pixel sizes; paintEvent owns that now. - Tests: rewrite the truncate case (now scales to fit), add a floor- truncation case, assert cell across all cases.
The .clang-tidy config demanded UPPER_CASE for all constants and enum constants, but the codebase uses Google-style kCamelCase for ~90 namespace constexpr (kCellSize, kIconBase, ...) and CamelCase enumerators (TileContext::Launcher, DragState::Idle). Switch GlobalConstantCase to CamelCase + 'k' prefix and EnumConstantCase to CamelCase so clangd stops flagging the prevailing convention. Only GlobalConstant (not the catch-all Constant) is constrained, so local const values stay unflagged instead of being asked to take the 'k' prefix. A handful of UPPER_CASE constants in base/ and the hybrid kDEFAULT_LEVEL are now flagged by the corrected standard — left as a separate cleanup.
When the taskbar narrows so not every tile fits, the row becomes horizontally scrollable: ‹ / › chevron buttons flank it (shown only on overflow) and each click eases it by one tile via a 220ms OutCubic animation, dimming the arrow at the scroll end. Tiles stay fixed at 56px and center with a relaxed 16px gap (Windows-like) when they all fit — no shrinking, no dropdown menu (too hard to hit on i.MX6ULL resistive touch). - CenteredTaskbar wraps icon_layout_ in a QScrollArea (NoFrame, hidden scrollbars, widgetResizable=false, AlignCenter) and drives the hidden horizontal scroll bar from the arrows; rangeChanged shows/hides them, valueChanged toggles enabled, QVariantAnimation smooths the glide. - New TaskbarScrollArrow widget: bare chevron (no filled background), opacity-encoded state (hover 1.0 / rest 0.8 / disabled 0.3). - TaskbarIcon.paintEvent derives geometry from width()/56 (defensive). - kTileSpacing=16, kScrollStride=72, kScrollAnimMs=220.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.