Skip to content

feature: icon layer#26

Merged
Charliechen114514 merged 9 commits into
mainfrom
feat/desktop-icon-layer
Jul 8, 2026
Merged

feature: icon layer#26
Charliechen114514 merged 9 commits into
mainfrom
feat/desktop-icon-layer

Conversation

@Charliechen114514

Copy link
Copy Markdown
Member

No description provided.

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.
@Charliechen114514 Charliechen114514 merged commit 8a077bb into main Jul 8, 2026
6 checks passed
@Charliechen114514 Charliechen114514 deleted the feat/desktop-icon-layer branch July 8, 2026 02:54
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.

1 participant