teksilo_core/styles/text_selection_style.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Tier-3 style protocol for the touch text-selection chrome — the selection
5//! handles and the magnifier that `crate::text_touch` raises.
6//!
7//! One of the few Tier-3 traits that returns **pure-data recipes only**, with
8//! no `make_*(cfg, ctx) -> WidgetId` — the others are
9//! [`ChartStyle`](crate::styles::ChartStyle) and
10//! [`GridViewStyle`](crate::styles::GridViewStyle), and for the same reason:
11//! the chrome is single-node batched paint. Here that is a disc on a stem and a
12//! framed lens whose interior is a replay of the host's own text layer. A
13//! composed subtree would buy nothing, while forcing the affordance widgets —
14//! which live in `teksilo-core` — to depend on `teksilo-widgets`.
15//!
16//! The shipped default, `RecipeTextSelectionStyle`, therefore lives in
17//! `teksilo-widgets` (`styles/recipe_text_selection_style.rs`); this crate
18//! holds the trait, the recipes and the `Rc<dyn TextSelectionStyle>` slot type.
19//!
20//! # Density
21//!
22//! A recipe is built once per density with `for_tokens(&InputTokens)`. The two
23//! dimensions are classified differently on purpose:
24//!
25//! * the **hit extent** is a [`TargetRole::Target`](teksilo_tokens::TargetRole)
26//! and routes through [`dp`](crate::styles::density::dp), so it can only grow
27//! with density;
28//! * the **painted diameter** is a
29//! [`Decoration`](teksilo_tokens::TargetRole::Decoration) and is the same at
30//! every density. It is chrome already sized to a fingertip, and target
31//! conformance is measured on the hit rectangle, not on the paint — the rule
32//! `docs/density-and-targets.md` states for any affordance whose hit area is
33//! widened rather than its ink.
34
35use std::rc::Rc;
36
37use crate::styles::{RecipeColor, Theme};
38
39/// Painted geometry and colours of one selection handle.
40///
41/// `hit_size` is the extent of the handle's own node — a square centred on the
42/// anchor point — and `diameter` is the disc drawn inside it. The disc is
43/// always centred in the hit square, so a caller that changes one without the
44/// other still gets a centred handle.
45#[derive(Debug, Clone, Copy, PartialEq)]
46pub struct TextSelectionHandleRecipe {
47 /// Diameter of the painted disc, in dp.
48 pub diameter: f32,
49 /// Extent of the square hit rectangle, in dp. Never below
50 /// `InputTokens::min_target_conformance`.
51 pub hit_size: f32,
52 /// Width of the stem drawn from the disc to the caret it marks, in dp.
53 /// Zero draws no stem.
54 pub stem_width: f32,
55 /// Fill of the disc and the stem.
56 pub fill: RecipeColor,
57 /// Ring drawn around the disc so it stays visible over text of its own
58 /// colour. Zero `outline_width` draws none.
59 pub outline: RecipeColor,
60 /// Width of that ring, in dp.
61 pub outline_width: f32,
62}
63
64/// Painted geometry and colours of the magnifier lens.
65///
66/// The lens interior is not painted by the style: it is a replay of the host's
67/// own text layer under a transform and a clip (see
68/// [`crate::text_touch::magnifier`]). The style owns only the frame around it.
69#[derive(Debug, Clone, Copy, PartialEq)]
70pub struct TextMagnifierRecipe {
71 /// Half the lens's width, in dp — the lens is `2 × radius` wide.
72 pub radius: f32,
73 /// Half the lens's height, in dp.
74 pub half_height: f32,
75 /// Magnification applied to the replayed content.
76 pub scale: f32,
77 /// How far above the contact point the lens's bottom edge sits, in dp.
78 pub rise: f32,
79 /// Corner radius of the painted frame, in dp.
80 ///
81 /// The **clip** is rectangular — see [`crate::text_touch::magnifier`] for
82 /// why, and for what shows in the corners because of it.
83 pub corner_radius: f32,
84 /// Fill painted behind the replayed content, so glyphs from the layer
85 /// beneath do not show through.
86 pub background: RecipeColor,
87 /// Frame drawn around the lens.
88 pub border: RecipeColor,
89 /// Width of that frame, in dp.
90 pub border_width: f32,
91}
92
93/// Tier-3 protocol for the touch text-selection chrome. See the module docs.
94pub trait TextSelectionStyle: 'static {
95 /// Geometry and colours of a selection handle.
96 fn handle(&self, theme: &Theme) -> TextSelectionHandleRecipe;
97 /// Geometry and colours of the magnifier frame.
98 fn magnifier(&self, theme: &Theme) -> TextMagnifierRecipe;
99}
100
101/// Shared handle type stored in
102/// [`ComponentStyleSlots::text_selection`](crate::styles::ComponentStyleSlots).
103pub type SharedTextSelectionStyle = Rc<dyn TextSelectionStyle>;