Skip to content

Hover overlays: draw Window elements over the hovered control instead of a fixed outline #112

Open

Description

@oglassdev

coreShaders.hover (#107) draws a 1px white rectangle around the hovered region. That is all it can draw: the shape is the region's rectangle, the color is fixed, and a slot showing an <Items> stack gets nothing. In a pack with rounded controls, icons that overhang their buttons, and rows whose tooltips are written at runtime, most controls end up with a hover that does not fit or with none.

This issue replaces the outline with hover overlays: any Window elements (frames, sprites, switches, bound text, bars, tints) drawn over a control while it is hovered, plus tooltips drawn by Window in the same way.

Decisions

  1. A hover is an element tree, not a fixed outline. hover takes children and they are drawn above the window while a slot of the region is hovered.
  2. The overlay rides in the hitbox item's tooltip, pinned horizontally. The shader shifts it in x and rebuilds its y. Details below.
  3. Overlay controls do not use the vanilla tooltip. Window draws the tooltip as part of the overlay.
  4. Hover moves to experimental (experimental: { hover: true }), next to anvilUpdates. It depends on vanilla's tooltip placement, the text shader, and pack formats 85 through 88.
  5. A by-hand spike comes first, because none of the mechanism has been run in game.

How it works

flowchart LR
  A["Build<br/>compiler lays out each overlay<br/>with the title pipeline"] --> B["Server<br/>writes the overlay run into the<br/>hitbox item's name and lore"]
  B --> C["Client<br/>draws the tooltip only<br/>while the slot is hovered"]
  C --> D["Text shader<br/>x: add one constant<br/>y: rebuild from the code in x"]
Loading

Pinning x

Vanilla places a tooltip at the cursor plus (12, -12). If it would run off the right edge, x becomes max(x - 24 - width, 4). A line wider than the screen therefore always starts at x = 4, wherever the cursor is.

Each overlay line is built so its net width is about 16384 px, using spacers. The tooltip box is hidden with the existing transparent hover tooltip style.

Marking and placing glyphs

Every overlay glyph is preceded by a large positive spacer and the line ends with a negative one, so the glyph is drawn far to the right while the line's width stays fixed. The glyph's x position then holds everything the shader needs:

Position.x  =  4  +  1024 × code  +  layout x
               │         │               └ 0–1023: where the glyph sits in the overlay
               │         └ about 14 bits: the run's target y and its glyph height
               └ where vanilla pins an over-wide tooltip
Part Bits Meaning
layout x 10 Horizontal position in the overlay, set by ordinary spacers
target y about 9 Top of the run, relative to the screen center
glyph height about 5 Height of the run's glyph quads
marker none code is always at least 16, so x is past 16384, where nothing else draws

A 32-bit float holds whole numbers exactly up to 2^24, which is where the 24-bit total comes from.

The shader then does two things to a marked vertex:

  • x: subtract the pinned origin and the code, add the screen center. Letters keep their natural spacing, so a run of bound text is one component.
  • y: discard it. A top vertex goes to the target y and a bottom vertex to the target y plus the height. Which corner a vertex is comes from its UV, as the current shader already works out.

Because y never depends on where the tooltip is, the tooltip can be one line tall, and the lines F3+H adds (the item id and component count) change nothing.

Colors and shadows

Position carries all the data, so text color and shadow color are the real ones. Both copies of a glyph see the same position and both are placed. Rebuilding y loses the shadow copy's 1px vertical offset, so overlay text sets its shadow alpha to 254 and the shader adds the pixel back for that alpha. Art glyphs set shadow alpha 0, which makes the client skip the shadow copy.

Tooltips

A tooltip is overlay content like any other, laid out at runtime:

  • The server measures the text, as it already does for the cut-out, and wraps it to a fixed maximum width.
  • Each row is a background strip, a negative spacer, then the row's text. Strips are a left cap, a right cap and middle pieces 1 to 128 px wide, in top, body and bottom variants: about 30 glyphs for any width and any number of rows.
  • Every row is its own run with its own target y, so rows are placed to the pixel.
  • The tooltip is anchored to the hovered slot (each slot has its own item) and goes below controls in the top half of the window and above the rest.

Runtime tooltips become bound text in the overlay. That removes the invisible tooltip stacks packs use today, which is what gives rows, cards and nodes a hover at all.

Authoring sketch

<Region onClick={buy} hover={<Box frame={art.buyLit} />} tooltip={tip} />

// States come from an ordinary switch inside the overlay.
<Region
  onClick={previous}
  hover={
    <Switch bind={canPrevious}>
      <Case value="true" frame={art.stepHover} />
      <Case value="false" frame={art.stepHoverDisabled} />
    </Switch>
  }
/>

<Region onClick={visit} hover tooltip={zoneTip} /> // a default outline; the tooltip is a handle Kotlin fills

hover as a boolean keeps drawing a default outline, through the new path. The data-pixel glyphs, the shadow-color packing and the tooltip cut-out are removed.

Checked against the 26.2 client

Read from the client jar and its core/text shader, not run in game:

  • DefaultTooltipPositioner: the placement and clamp rules above. Item tooltips in containers use it.
  • Tooltip size is the widest line by 10 px per line (8 px for one line).
  • A bitmap provider's ascent is any integer not greater than height; a space provider's advances were not checked.
  • A shadow color with alpha 0 draws no shadow copy; any other alpha reaches the shader unchanged when the text is opaque.
  • With advanced tooltips the client appends two lines to an item like the hitbox.
  • Text is queued for drawing unless it has no bounds; no screen culling was found at that point.
  • The GUI text vertex shader receives Position, Color, UV0, the atlas, ProjMat and the Globals block (ScreenSize, GameTime). It has no cursor position.

Spike

One hand-built item and a small shader edit, to confirm:

  • The tooltip pins at x = 4 with a 16384 px line, at several window sizes and GUI scales.
  • A glyph placed millions of pixels to the right is still drawn, and spacers that large are accepted.
  • The x shift and rebuilt y land on the pixel, for an art glyph and for a run of vanilla-font text.
  • The hidden tooltip box costs nothing at that width.
  • F3+H and a tooltip at the bottom of the screen change nothing.

Done when

  • hover on <Region> and the industrial controls takes elements, including switches, bound text, bound sprites and tints, and draws them while the region is hovered.
  • A region's tooltip, static or from a handle, is drawn by Window and sized to its text.
  • A slot showing a stack can be part of a hover region.
  • Hover is enabled by experimental.hover; coreShaders.hover is a build error that names the new option.
  • Previews can draw a control hovered.
  • AUTHORING.md describes overlays, the tooltip, the limits and what is client-version dependent.

Limits and open questions

  • Glyph height. With about 5 bits a glyph is at most 32 px tall, so the compiler slices taller art into bands. Fewer layout bits would allow taller glyphs.
  • Text heights. The shader needs each run's glyph height. A pack font has one; vanilla's default font mixes several, so the runtime needs a table and may split runs.
  • Slots with a real stack. The slot has one tooltip. Either the stack keeps its vanilla tooltip and the slot has no overlay, or the overlay replaces it. Probably a prop.
  • Updates. An overlay with bound values means resending the hitbox item when they change.
  • Always on top. The tooltip layer draws above the window's text and items. An overlay can cover or tint what is under it, not go beneath.
  • Client mods. x relies on vanilla's clamp, so a mod that moves or redraws tooltips can misplace overlays horizontally.
  • Tooltip art. Where a pack sets the tooltip's frame and text style.
  • Wide screens. A 16384 px line pins any screen narrower than that.

Considered and dropped

  • Art in the existing hover glyph. Keeps data pixels in the texture and gives one piece of art per region. No text.
  • Placing every glyph from its shadow color. Text needs a component per character with its width, and the 32 bits do not hold position, size, cut-out and color.
  • Pinning y with 256 lore lines. Works, but F3+H then moves every overlay 20 px, and every hitbox item carries the padding.
  • y in a font's ascent. Needs a font per art per row and still leaves the glyph's size unknown.
  • A table of rectangles in the shader, indexed by an id. Fine for static rectangles, no help for content laid out at runtime.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions