Skip to content

Commit d85aa3b

Browse files
authored
Merge pull request locainin#14 from locainin/dev
see pull request locainin#14 for details
2 parents c8519d3 + 5af8c6d commit d85aa3b

210 files changed

Lines changed: 5518 additions & 3759 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/noticenterctl/src/main/main_css_check_geometry/media/width.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ impl GeometryModel {
1616
// No media widget means no media width pressure
1717
return None;
1818
}
19-
// Width warnings compare the final shell pressure to the configured panel width
20-
let required_panel_width_px = self.media_required_panel_width_px(config);
19+
// Width warnings compare the minimum shell pressure to the configured panel width
20+
let required_panel_width_px = self.media_minimum_panel_width_px(config);
2121
width_warning(
2222
"media row",
2323
required_panel_width_px,
@@ -42,26 +42,22 @@ impl GeometryModel {
4242
))
4343
}
4444

45-
fn media_required_panel_width_px(&self, config: &Config) -> i32 {
46-
// Stock pressure is used as the baseline because the final warning talks in panel width
47-
let pressure = self.media_pressure_px(config);
45+
fn media_minimum_panel_width_px(&self, config: &Config) -> i32 {
46+
let pressure = self.media_minimum_pressure_px(config);
4847
let stock = stock_geometry_model();
4948
let stock_config = stock_config();
50-
let stock_pressure = stock.media_pressure_px(stock_config);
49+
let stock_pressure = stock.media_minimum_pressure_px(stock_config);
5150

52-
// The delta from stock is easier to reason about than an absolute guess
5351
stock_config.panel.width + (pressure - stock_pressure)
5452
}
5553

56-
fn media_pressure_px(&self, config: &Config) -> i32 {
54+
fn media_minimum_pressure_px(&self, config: &Config) -> i32 {
5755
// The shell snapshot keeps the width math deterministic across helper calls
5856
let shell = ModeledMediaShell::from_config(&config.media);
59-
let text_width_px = config
60-
.panel
61-
.width
62-
.saturating_sub(media_text_reserve_px(shell))
63-
.max(shell.text_width_floor_px.max(MEDIA_TEXT_WIDTH_FLOOR_PX));
64-
// The text lane is fixed by the widget builder, but meta padding can still widen it
57+
// The runtime title lane grows with the panel, but minimum-width lint must stay
58+
// actionable. Using the floor prevents required width from growing with panel.width
59+
let text_width_px = shell.text_width_floor_px.max(MEDIA_TEXT_WIDTH_FLOOR_PX);
60+
// The text lane has a configured minimum, but meta padding can still widen it
6561
let meta_width_px = self
6662
.media_meta
6763
.outer_width_px(text_width_px)
@@ -192,6 +188,7 @@ impl GeometryModel {
192188
}
193189
}
194190

191+
#[cfg(test)]
195192
pub(super) fn media_text_reserve_px(shell: ModeledMediaShell) -> i32 {
196193
// Reserve math mirrors the runtime shell so marquee width stays in sync with widget layout
197194
let mut reserve_px = 0;

crates/noticenterctl/src/main/main_css_check_geometry/model/constants.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@
22
pub(super) const WIDTH_WARNING_TOLERANCE_PX: i32 = 8;
33
pub(super) const HEIGHT_WARNING_TOLERANCE_PX: i32 = 12;
44

5-
// Fixed-grid widgets share column counts, spacing, and fallback content widths
6-
pub(super) const TOGGLE_GRID_COLUMNS: usize = 4;
5+
// Fixed-grid widgets share spacing and fallback content widths
76
pub(super) const TOGGLE_GRID_SPACING_PX: i32 = 8;
87
pub(super) const TOGGLE_FALLBACK_CONTENT_WIDTH_PX: i32 = 80;
98

10-
pub(super) const STAT_GRID_COLUMNS: usize = 2;
119
pub(super) const STAT_GRID_SPACING_PX: i32 = 8;
1210
pub(super) const STAT_FALLBACK_CONTENT_WIDTH_PX: i32 = 96;
1311

14-
pub(super) const CARD_GRID_COLUMNS: usize = 2;
1512
pub(super) const CARD_GRID_SPACING_PX: i32 = 8;
1613
pub(super) const CARD_FALLBACK_CONTENT_WIDTH_PX: i32 = 104;
1714

Lines changed: 97 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use unixnotis_core::Config;
22

33
use super::constants::{
4-
CARD_FALLBACK_CONTENT_WIDTH_PX, CARD_GRID_COLUMNS, CARD_GRID_SPACING_PX,
5-
STAT_FALLBACK_CONTENT_WIDTH_PX, STAT_GRID_COLUMNS, STAT_GRID_SPACING_PX,
6-
TOGGLE_FALLBACK_CONTENT_WIDTH_PX, TOGGLE_GRID_COLUMNS, TOGGLE_GRID_SPACING_PX,
4+
CARD_FALLBACK_CONTENT_WIDTH_PX, CARD_GRID_SPACING_PX, STAT_FALLBACK_CONTENT_WIDTH_PX,
5+
STAT_GRID_SPACING_PX, TOGGLE_FALLBACK_CONTENT_WIDTH_PX, TOGGLE_GRID_SPACING_PX,
76
};
87
use super::{
98
stock_config, stock_geometry_model, width_warning, GeometryModel, HorizontalBoxMetrics,
@@ -27,7 +26,7 @@ impl GeometryModel {
2726
"stat grid",
2827
required_panel_width_px,
2928
config.panel.width,
30-
"GTK natural width can still widen the panel when two-column cards ask for more room",
29+
"GTK natural width can still widen the panel when configured stat columns ask for more room",
3130
)
3231
}
3332

@@ -37,7 +36,7 @@ impl GeometryModel {
3736
"card grid",
3837
required_panel_width_px,
3938
config.panel.width,
40-
"GTK natural width can still widen the panel when two-column cards ask for more room",
39+
"GTK natural width can still widen the panel when configured card columns ask for more room",
4140
)
4241
}
4342

@@ -49,7 +48,7 @@ impl GeometryModel {
4948
.iter()
5049
.filter(|widget| widget.enabled)
5150
.count()
52-
.min(TOGGLE_GRID_COLUMNS);
51+
.min(config.widgets.toggle_columns);
5352
if columns == 0 {
5453
return None;
5554
}
@@ -64,38 +63,40 @@ impl GeometryModel {
6463
.outer_width_px(TOGGLE_FALLBACK_CONTENT_WIDTH_PX),
6564
);
6665

67-
// Compare against the shipped theme so stock CSS stays quiet
68-
let stock = stock_geometry_model();
69-
let stock_config = stock_config();
70-
let stock_columns = stock_config
71-
.widgets
72-
.toggles
73-
.iter()
74-
.filter(|widget| widget.enabled)
75-
.count()
76-
.min(TOGGLE_GRID_COLUMNS);
77-
let stock_pressure = stock.fixed_grid_pressure_px(
78-
stock_columns,
79-
TOGGLE_GRID_SPACING_PX,
80-
stock.toggle_section,
81-
stock.toggle_grid,
82-
stock
83-
.toggle_item
84-
.outer_width_px(TOGGLE_FALLBACK_CONTENT_WIDTH_PX),
85-
);
86-
87-
Some(stock_config.panel.width + (pressure - stock_pressure))
66+
Some(self.required_width_from_pressure(
67+
pressure,
68+
config.widgets.toggle_columns,
69+
stock_config().widgets.toggle_columns,
70+
|stock, stock_config| {
71+
let stock_columns = stock_config
72+
.widgets
73+
.toggles
74+
.iter()
75+
.filter(|widget| widget.enabled)
76+
.count()
77+
.min(stock_config.widgets.toggle_columns);
78+
stock.fixed_grid_pressure_px(
79+
stock_columns,
80+
TOGGLE_GRID_SPACING_PX,
81+
stock.toggle_section,
82+
stock.toggle_grid,
83+
stock
84+
.toggle_item
85+
.outer_width_px(TOGGLE_FALLBACK_CONTENT_WIDTH_PX),
86+
)
87+
},
88+
))
8889
}
8990

9091
fn stat_required_panel_width_px(&self, config: &Config) -> Option<i32> {
91-
// Stats render as a fixed two-column grid, so enabled items decide the live column count
92+
// Enabled stats decide how much of the configured column budget is actually used
9293
let columns = config
9394
.widgets
9495
.stats
9596
.iter()
9697
.filter(|widget| widget.enabled)
9798
.count()
98-
.min(STAT_GRID_COLUMNS);
99+
.min(config.widgets.stat_columns);
99100
if columns == 0 {
100101
return None;
101102
}
@@ -109,26 +110,29 @@ impl GeometryModel {
109110
.outer_width_px(STAT_FALLBACK_CONTENT_WIDTH_PX),
110111
);
111112

112-
let stock = stock_geometry_model();
113-
let stock_config = stock_config();
114-
let stock_columns = stock_config
115-
.widgets
116-
.stats
117-
.iter()
118-
.filter(|widget| widget.enabled)
119-
.count()
120-
.min(STAT_GRID_COLUMNS);
121-
let stock_pressure = stock.fixed_grid_pressure_px(
122-
stock_columns,
123-
STAT_GRID_SPACING_PX,
124-
stock.stat_section,
125-
stock.stat_grid,
126-
stock
127-
.stat_item
128-
.outer_width_px(STAT_FALLBACK_CONTENT_WIDTH_PX),
129-
);
130-
131-
Some(stock_config.panel.width + (pressure - stock_pressure))
113+
Some(self.required_width_from_pressure(
114+
pressure,
115+
config.widgets.stat_columns,
116+
stock_config().widgets.stat_columns,
117+
|stock, stock_config| {
118+
let stock_columns = stock_config
119+
.widgets
120+
.stats
121+
.iter()
122+
.filter(|widget| widget.enabled)
123+
.count()
124+
.min(stock_config.widgets.stat_columns);
125+
stock.fixed_grid_pressure_px(
126+
stock_columns,
127+
STAT_GRID_SPACING_PX,
128+
stock.stat_section,
129+
stock.stat_grid,
130+
stock
131+
.stat_item
132+
.outer_width_px(STAT_FALLBACK_CONTENT_WIDTH_PX),
133+
)
134+
},
135+
))
132136
}
133137

134138
fn card_required_panel_width_px(&self, config: &Config) -> Option<i32> {
@@ -139,7 +143,7 @@ impl GeometryModel {
139143
.iter()
140144
.filter(|widget| widget.enabled)
141145
.count()
142-
.min(CARD_GRID_COLUMNS);
146+
.min(config.widgets.card_columns);
143147
if columns == 0 {
144148
return None;
145149
}
@@ -153,26 +157,29 @@ impl GeometryModel {
153157
.outer_width_px(CARD_FALLBACK_CONTENT_WIDTH_PX),
154158
);
155159

156-
let stock = stock_geometry_model();
157-
let stock_config = stock_config();
158-
let stock_columns = stock_config
159-
.widgets
160-
.cards
161-
.iter()
162-
.filter(|widget| widget.enabled)
163-
.count()
164-
.min(CARD_GRID_COLUMNS);
165-
let stock_pressure = stock.fixed_grid_pressure_px(
166-
stock_columns,
167-
CARD_GRID_SPACING_PX,
168-
stock.card_section,
169-
stock.card_grid,
170-
stock
171-
.card_item
172-
.outer_width_px(CARD_FALLBACK_CONTENT_WIDTH_PX),
173-
);
174-
175-
Some(stock_config.panel.width + (pressure - stock_pressure))
160+
Some(self.required_width_from_pressure(
161+
pressure,
162+
config.widgets.card_columns,
163+
stock_config().widgets.card_columns,
164+
|stock, stock_config| {
165+
let stock_columns = stock_config
166+
.widgets
167+
.cards
168+
.iter()
169+
.filter(|widget| widget.enabled)
170+
.count()
171+
.min(stock_config.widgets.card_columns);
172+
stock.fixed_grid_pressure_px(
173+
stock_columns,
174+
CARD_GRID_SPACING_PX,
175+
stock.card_section,
176+
stock.card_grid,
177+
stock
178+
.card_item
179+
.outer_width_px(CARD_FALLBACK_CONTENT_WIDTH_PX),
180+
)
181+
},
182+
))
176183
}
177184

178185
fn fixed_grid_pressure_px(
@@ -193,4 +200,24 @@ impl GeometryModel {
193200
// Spacing only exists between neighbors, never after the last item
194201
+ ((columns.saturating_sub(1)) as i32 * spacing_px)
195202
}
203+
204+
fn required_width_from_pressure<F>(
205+
&self,
206+
pressure: i32,
207+
configured_columns: usize,
208+
stock_columns: usize,
209+
stock_pressure_for_default: F,
210+
) -> i32
211+
where
212+
F: FnOnce(&GeometryModel, &Config) -> i32,
213+
{
214+
if configured_columns != stock_columns {
215+
return pressure;
216+
}
217+
218+
let stock = stock_geometry_model();
219+
let stock_config = stock_config();
220+
let stock_pressure = stock_pressure_for_default(stock, stock_config);
221+
stock_config.panel.width + (pressure - stock_pressure)
222+
}
196223
}

crates/noticenterctl/src/main/main_css_check_geometry/parse/selectors.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ pub(super) fn maybe_warn_for_complex_unixnotis_selector(
2323
// Shipped complex selectors already have a known baseline and should stay quiet
2424
return;
2525
}
26+
if selector_targets_descendant(selector) && !selector_target_mentions_unixnotis_class(selector)
27+
{
28+
// Descendant rules aimed at GTK subnodes usually affect an inner allocation
29+
// Rules whose final target is a UnixNotis hook still need a warning because they can
30+
// resize a real widget that this lightweight model cannot update from a complex selector
31+
return;
32+
}
2633

2734
let warning_key = format!("complex:{selector}");
2835
if !warned_classes.insert(warning_key) {
@@ -36,6 +43,32 @@ pub(super) fn maybe_warn_for_complex_unixnotis_selector(
3643
));
3744
}
3845

46+
fn selector_targets_descendant(selector: &str) -> bool {
47+
selector.contains(' ')
48+
|| selector.contains('>')
49+
|| selector.contains('+')
50+
|| selector.contains('~')
51+
}
52+
53+
fn selector_target_mentions_unixnotis_class(selector: &str) -> bool {
54+
selector_mentions_unixnotis_class(rightmost_selector_segment(selector))
55+
}
56+
57+
fn rightmost_selector_segment(selector: &str) -> &str {
58+
let split_at = selector
59+
.char_indices()
60+
.filter_map(|(index, ch)| {
61+
if ch.is_ascii_whitespace() || matches!(ch, '>' | '+' | '~') {
62+
Some(index + ch.len_utf8())
63+
} else {
64+
None
65+
}
66+
})
67+
.next_back()
68+
.unwrap_or(0);
69+
selector[split_at..].trim()
70+
}
71+
3972
pub(super) fn simple_class_selector(selector: &str) -> Option<&str> {
4073
let trimmed = selector.trim();
4174
if !trimmed.starts_with('.') {

crates/noticenterctl/src/main/main_css_check_geometry/stock/classes.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fn collect_unixnotis_classes(css: &'static str, classes: &mut HashSet<&'static s
4949
}
5050
}
5151

52-
fn hook_unixnotis_classes() -> [&'static str; 27] {
52+
fn hook_unixnotis_classes() -> [&'static str; 33] {
5353
// Hook-only classes can be real live selectors before the stock theme gives them rules
5454
[
5555
".unixnotis-panel-actions",
@@ -65,6 +65,12 @@ fn hook_unixnotis_classes() -> [&'static str; 27] {
6565
".unixnotis-panel-action-close",
6666
".unixnotis-panel-action-with-icon",
6767
".unixnotis-panel-action-icon",
68+
".unixnotis-panel-subtitle",
69+
".unixnotis-section-header",
70+
".unixnotis-recent-section",
71+
".unixnotis-recent-header",
72+
".unixnotis-recent-header-row",
73+
".unixnotis-panel-footer",
6874
".unixnotis-group",
6975
".unixnotis-group-row",
7076
".unixnotis-group-header",
@@ -94,4 +100,15 @@ mod tests {
94100
assert!(classes.contains(".unixnotis-media-button-play"));
95101
assert!(classes.contains(".unixnotis-media-button-next"));
96102
}
103+
104+
#[test]
105+
fn section_header_hooks_are_treated_as_known_public_classes() {
106+
let classes = known_unixnotis_classes();
107+
108+
assert!(classes.contains(".unixnotis-section-header"));
109+
assert!(classes.contains(".unixnotis-recent-section"));
110+
assert!(classes.contains(".unixnotis-recent-header"));
111+
assert!(classes.contains(".unixnotis-recent-header-row"));
112+
assert!(classes.contains(".unixnotis-panel-footer"));
113+
}
97114
}

0 commit comments

Comments
 (0)