Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,22 @@ A toggle, a scene activation or a script run.
_Avoid_: tap action, default action

**Axis**:
One numeric dimension of an entity that is set rather than toggled, such as brightness, white colour temperature, thermostat setpoint or cover position.
One numeric dimension of an entity that is set rather than toggled, such as brightness, white colour temperature, hue, thermostat setpoint or cover position.
An entity may expose several at once, each with its own range reported by Home Assistant.
An axis need not have a control of its own: hue and saturation are two axes driven by a single colour surface.
_Avoid_: slider, analog, channel

**Absent axis**:
An axis Home Assistant is currently reporting as `null`, meaning the device is not driving that dimension at all.
Absent is a reading of the present, not a gap in the record, and it is rendered as such: dimmed, with no knob and no readout.
It is never rendered as the axis minimum, for reasons recorded in `docs/adr/0006-a-null-axis-renders-as-absent.md`.
_Avoid_: missing, unknown, zero, unset

**Control**:
What the user grabs in an expanded widget, and the owner of three things: how it draws, what it sends, and how it recognises its own echo.
A control may drive more than one axis, which is what separates it from an axis and is recorded in `docs/adr/0004-controls-and-axes.md`.
_Avoid_: slider, input, field

**Armed**:
A widget with confirmation enabled that has taken its first tap and is awaiting a second.
Being armed has a bounded life: it ends on confirmation, on cancellation, on a timeout, or when the pointer leaves the widget.
Expand Down
121 changes: 121 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,22 @@ diagnostics = []
anyhow = "1.0.102"
auto-launch = "0.6.0"
chrono = { version = "0.4.44", default-features = false, features = ["clock"] }
# `image-without-codecs` rather than `image`: we only ever hand iced a
# buffer of RGBA bytes we computed ourselves (see `ui::colour_texture`),
# so none of the `image` crate's format decoders are wanted. The plain
# `image` feature turns them all on and drags PNG, JPEG, GIF and the
# rest into the binary for nothing.
# `advanced` for `iced::advanced::Widget`: the colour surface is a
# widget of our own, because a slider cannot express two axes and a
# `mouse_area` cannot say *where* inside itself it was pressed. See
# `ui::colour_field`.
iced = { version = "0.14.0", features = [
"wgpu",
"tokio",
"markdown",
"sysinfo",
"image-without-codecs",
"advanced",
] }
iced_winit = { version = "0.14.0", features = ["sysinfo"] }
directories = "6.0.0"
Expand Down Expand Up @@ -97,6 +108,7 @@ keyring = { version = "3.6.3", features = [

[dev-dependencies]
tempfile = "3.27.0"
wiremock = "0.6.5"

[patch.crates-io]
iced = { git = "https://github.com/schizza/iced.git", tag = "snapdash-v0.14.0-p10" }
Expand Down
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ If the token is compromised: delete it in HA, generate a new one, paste it into
| --- | --- |
| `sensor.*`, `binary_sensor.*` | Read-only — value, unit and last-changed detail |
| `switch.*` | Read + tap to **toggle** |
| `light.*` | Read + tap to **toggle** (on/off only — no brightness or color yet) |
| `light.*` | Read + tap to **toggle**, expand to set brightness, white temperature and colour |
| `input_boolean.*` | Read + tap to **toggle** |
| `scene.*` | Tap to **activate** the scene |
| `script.*` | Tap to **run** the script |
Expand Down Expand Up @@ -201,13 +201,43 @@ Widgets are frameless — the controls appear on hover:
| Control | Position | Does |
| --- | --- | --- |
| **Action button** | top-right | Triggers the entity's action (actionable entities only, while connected) |
| **Chevron** | top-right | Expands the widget to reveal its continuous controls (entities that have any, while connected) |
| **Update icon** | top-right | Shown when a new Snapdash release is available — opens the release notes |
| **Sliders** | right edge | Opens this widget's own settings dialog |
| **Priority dots** | bottom-left | Quick Low / Normal / High switch |
| **Gear** | bottom-right | Opens the app Settings window |

Dragging anywhere on the card moves the widget; the position is persisted.

### Adjusting a value

Tap the chevron and the card grows downwards to reveal a control for everything the entity has to set: a slider for a light's brightness, another for its white temperature, and a colour field.
Tap it again to put them away.
The controls belong to the widget rather than to a window of their own, so they cannot drift away from the value they set, and each one calls Home Assistant as you drag.

The colour field is two-dimensional - hue runs left to right, saturation top to bottom - and one drag sets both at once, as a single `hs_color`.
That is a lot of colour in a small space: at the Small preset the field is 132 points wide and covers all 359 degrees of hue, so one point of movement is worth nearly three degrees.
Three shortcuts answer that, and none of them is a mode you can get stuck in.

| Hold | Does |
| --- | --- |
| **Shift** | Holds whichever axis has moved less since you pressed, so you can sweep the hue without disturbing the saturation |
| **Alt** (Option on macOS) | Keeps a quarter of the movement, measured from where you pressed, so the fine adjustment carries on from where the coarse one had got to |
| **Wheel** | Nudges the hue one step at a time, and the saturation with Shift held |

Letting go of the key is the whole of undoing it.
Nothing is remembered between one frame and the next, so a modifier cannot get stuck and a released one stops applying immediately.

The two ends of the saturation axis pull the last few points onto exactly 0% and exactly 100%, so white and full colour are not one-pixel targets.
Hue has no such magnet, because 0 and 359 are the same red and there is nothing at either end worth snapping to.

The `?` beside the colour readout names the three shortcuts on hover.
It appears there and nowhere else: the sliders are ordinary sliders, and Shift, Alt and the wheel do nothing on them.

While the controls are up, the hover chrome is gone - not dimmed, absent.
Every pixel the card grew by is a control, and a priority dot sitting across a slider is one you cannot drag.
Collapse the widget to get the chrome back.

## Settings

The Settings window is split into pages:
Expand Down
Loading
Loading