1use std::collections::HashMap;
5
6use crate::widget::nav_bar;
7use cosmic_config::CosmicConfigEntry;
8use cosmic_theme::ThemeMode;
9use iced::{Limits, Size, window};
10use iced_core::window::Id;
11use palette::Srgba;
12use slotmap::Key;
13
14use crate::Theme;
15
16#[derive(Clone)]
18pub struct NavBar {
19 active: bool,
20 context_id: crate::widget::nav_bar::Id,
21 toggled: bool,
22 toggled_condensed: bool,
23}
24
25#[allow(clippy::struct_excessive_bools)]
27#[derive(Clone)]
28pub struct Window {
29 pub header_title: String,
31 pub use_template: bool,
32 pub content_container: bool,
33 pub context_is_overlay: bool,
34 pub sharp_corners: bool,
35 pub show_context: bool,
36 pub show_headerbar: bool,
37 pub show_window_menu: bool,
38 pub show_close: bool,
39 pub show_maximize: bool,
40 pub show_minimize: bool,
41 height: f32,
42 width: f32,
43}
44
45#[derive(Clone)]
47pub struct Core {
48 pub debug: bool,
50
51 pub(super) icon_theme_override: bool,
53
54 is_condensed: bool,
56
57 pub(super) keyboard_nav: bool,
59
60 nav_bar: NavBar,
62
63 scale_factor: f32,
65
66 pub(super) focused_window: Option<window::Id>,
68
69 pub(super) theme_sub_counter: u64,
70 pub(super) system_theme: Theme,
72
73 pub(super) system_theme_mode: ThemeMode,
75
76 pub(super) portal_is_dark: Option<bool>,
77
78 pub(super) portal_accent: Option<Srgba>,
79
80 pub(super) portal_is_high_contrast: Option<bool>,
81
82 pub(super) title: HashMap<Id, String>,
83
84 pub window: Window,
85
86 #[cfg(feature = "applet")]
87 pub applet: crate::applet::Context,
88
89 #[cfg(feature = "single-instance")]
90 pub(crate) single_instance: bool,
91
92 #[cfg(feature = "dbus-config")]
93 pub(crate) settings_daemon: Option<cosmic_settings_daemon::CosmicSettingsDaemonProxy<'static>>,
94
95 pub(crate) main_window: Option<window::Id>,
96
97 pub(crate) exit_on_main_window_closed: bool,
98
99 pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>,
100}
101
102impl Default for Core {
103 fn default() -> Self {
104 Self {
105 debug: false,
106 icon_theme_override: false,
107 is_condensed: false,
108 keyboard_nav: true,
109 nav_bar: NavBar {
110 active: true,
111 context_id: crate::widget::nav_bar::Id::null(),
112 toggled: true,
113 toggled_condensed: false,
114 },
115 scale_factor: 1.0,
116 title: HashMap::new(),
117 theme_sub_counter: 0,
118 system_theme: crate::theme::active(),
119 system_theme_mode: ThemeMode::config()
120 .map(|c| {
121 ThemeMode::get_entry(&c).unwrap_or_else(|(errors, mode)| {
122 for why in errors.into_iter().filter(cosmic_config::Error::is_err) {
123 tracing::error!(?why, "ThemeMode config entry error");
124 }
125 mode
126 })
127 })
128 .unwrap_or_default(),
129 window: Window {
130 header_title: String::new(),
131 use_template: true,
132 content_container: true,
133 context_is_overlay: true,
134 sharp_corners: false,
135 show_context: false,
136 show_headerbar: true,
137 show_close: true,
138 show_maximize: true,
139 show_minimize: true,
140 show_window_menu: false,
141 height: 0.,
142 width: 0.,
143 },
144 focused_window: None,
145 #[cfg(feature = "applet")]
146 applet: crate::applet::Context::default(),
147 #[cfg(feature = "single-instance")]
148 single_instance: false,
149 #[cfg(feature = "dbus-config")]
150 settings_daemon: None,
151 portal_is_dark: None,
152 portal_accent: None,
153 portal_is_high_contrast: None,
154 main_window: None,
155 exit_on_main_window_closed: true,
156 menu_bars: HashMap::new(),
157 }
158 }
159}
160
161impl Core {
162 #[must_use]
164 #[inline]
165 pub const fn is_condensed(&self) -> bool {
166 self.is_condensed
167 }
168
169 #[must_use]
171 #[inline]
172 pub const fn scale_factor(&self) -> f32 {
173 self.scale_factor
174 }
175
176 #[inline]
178 pub const fn set_keyboard_nav(&mut self, enabled: bool) {
179 self.keyboard_nav = enabled;
180 }
181
182 #[must_use]
184 #[inline]
185 pub const fn keyboard_nav(&self) -> bool {
186 self.keyboard_nav
187 }
188
189 #[cold]
191 pub(crate) fn set_scale_factor(&mut self, factor: f32) {
192 self.scale_factor = factor;
193 self.is_condensed_update();
194 }
195
196 #[inline]
198 pub fn set_header_title(&mut self, title: String) {
199 self.window.header_title = title;
200 }
201
202 #[inline]
203 pub(crate) fn show_content(&self) -> bool {
205 !self.is_condensed || !self.nav_bar.toggled_condensed
206 }
207
208 #[allow(clippy::cast_precision_loss)]
209 fn is_condensed_update(&mut self) {
211 let mut breakpoint = 280.0 + 8.0 + 360.0;
213 if self.window.show_context && !self.window.context_is_overlay {
215 breakpoint += 344.0 + 8.0;
217 };
218 self.is_condensed = (breakpoint * self.scale_factor) > self.window.width;
219 self.nav_bar_update();
220 }
221
222 #[inline]
223 fn condensed_conflict(&self) -> bool {
224 self.is_condensed
226 && self.nav_bar.toggled_condensed
227 && self.window.show_context
228 && !self.window.context_is_overlay
229 }
230
231 #[inline]
232 pub(crate) fn context_width(&self, has_nav: bool) -> f32 {
233 let window_width = self.window.width / self.scale_factor;
234
235 let mut reserved_width = 360.0 + 8.0;
237 if has_nav {
238 reserved_width += 280.0 + 8.0;
240 }
241
242 #[allow(clippy::manual_clamp)]
243 (window_width - reserved_width).min(480.0).max(344.0)
247 }
248
249 #[cold]
250 pub fn set_show_context(&mut self, show: bool) {
251 self.window.show_context = show;
252 self.is_condensed_update();
253 if self.condensed_conflict() {
255 self.nav_bar.toggled_condensed = false;
256 self.is_condensed_update();
257 }
258 }
259
260 #[inline]
261 pub fn main_window_is(&self, id: iced::window::Id) -> bool {
262 self.main_window_id().is_some_and(|main_id| main_id == id)
263 }
264
265 #[must_use]
267 #[inline]
268 pub const fn nav_bar_active(&self) -> bool {
269 self.nav_bar.active
270 }
271
272 #[inline]
273 pub fn nav_bar_toggle(&mut self) {
274 self.nav_bar.toggled = !self.nav_bar.toggled;
275 self.nav_bar_set_toggled_condensed(self.nav_bar.toggled);
276 }
277
278 #[inline]
279 pub fn nav_bar_toggle_condensed(&mut self) {
280 self.nav_bar_set_toggled_condensed(!self.nav_bar.toggled_condensed);
281 }
282
283 #[inline]
284 pub(crate) const fn nav_bar_context(&self) -> nav_bar::Id {
285 self.nav_bar.context_id
286 }
287
288 #[inline]
289 pub(crate) fn nav_bar_set_context(&mut self, id: nav_bar::Id) {
290 self.nav_bar.context_id = id;
291 }
292
293 #[inline]
294 pub fn nav_bar_set_toggled(&mut self, toggled: bool) {
295 self.nav_bar.toggled = toggled;
296 self.nav_bar_set_toggled_condensed(self.nav_bar.toggled);
297 }
298
299 #[cold]
300 pub(crate) fn nav_bar_set_toggled_condensed(&mut self, toggled: bool) {
301 self.nav_bar.toggled_condensed = toggled;
302 self.nav_bar_update();
303 if self.condensed_conflict() {
305 self.window.show_context = false;
306 self.is_condensed_update();
307 if !self.is_condensed {
309 self.nav_bar.toggled = toggled;
310 self.nav_bar_update();
311 }
312 }
313 }
314
315 #[inline]
316 pub(crate) fn nav_bar_update(&mut self) {
317 self.nav_bar.active = if self.is_condensed {
318 self.nav_bar.toggled_condensed
319 } else {
320 self.nav_bar.toggled
321 };
322 }
323
324 #[inline]
325 pub(crate) const fn set_window_height(&mut self, new_height: f32) {
327 self.window.height = new_height;
328 }
329
330 #[inline]
331 pub(crate) fn set_window_width(&mut self, new_width: f32) {
333 self.window.width = new_width;
334 self.is_condensed_update();
335 }
336
337 #[inline]
338 pub const fn system_theme(&self) -> &Theme {
340 &self.system_theme
341 }
342
343 #[inline]
344 #[must_use]
345 pub const fn system_theme_mode(&self) -> ThemeMode {
347 self.system_theme_mode
348 }
349
350 pub fn watch_config<
351 T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone + PartialEq,
352 >(
353 &self,
354 config_id: &'static str,
355 ) -> iced::Subscription<cosmic_config::Update<T>> {
356 #[cfg(feature = "dbus-config")]
357 if let Some(settings_daemon) = self.settings_daemon.clone() {
358 return cosmic_config::dbus::watcher_subscription(settings_daemon, config_id, false);
359 }
360 cosmic_config::config_subscription(
361 std::any::TypeId::of::<T>(),
362 std::borrow::Cow::Borrowed(config_id),
363 T::VERSION,
364 )
365 }
366
367 pub fn watch_state<
368 T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone + PartialEq,
369 >(
370 &self,
371 state_id: &'static str,
372 ) -> iced::Subscription<cosmic_config::Update<T>> {
373 #[cfg(feature = "dbus-config")]
374 if let Some(settings_daemon) = self.settings_daemon.clone() {
375 return cosmic_config::dbus::watcher_subscription(settings_daemon, state_id, true);
376 }
377 cosmic_config::config_subscription(
378 std::any::TypeId::of::<T>(),
379 std::borrow::Cow::Borrowed(state_id),
380 T::VERSION,
381 )
382 }
383
384 #[must_use]
386 #[inline]
387 pub const fn focused_window(&self) -> Option<window::Id> {
388 self.focused_window
389 }
390
391 #[must_use]
393 #[inline]
394 pub fn system_is_dark(&self) -> bool {
395 self.portal_is_dark
396 .unwrap_or(self.system_theme_mode.is_dark)
397 }
398
399 #[must_use]
401 #[inline]
402 pub fn main_window_id(&self) -> Option<window::Id> {
403 self.main_window.filter(|id| iced::window::Id::NONE != *id)
404 }
405
406 #[inline]
408 pub fn set_main_window_id(&mut self, mut id: Option<window::Id>) -> Option<window::Id> {
409 std::mem::swap(&mut self.main_window, &mut id);
410 id
411 }
412
413 #[cfg(feature = "winit")]
414 pub fn drag<M: Send + 'static>(&self, id: Option<window::Id>) -> crate::app::Task<M> {
415 let Some(id) = id.or(self.main_window) else {
416 return iced::Task::none();
417 };
418 crate::command::drag(id)
419 }
420
421 #[cfg(feature = "winit")]
422 pub fn maximize<M: Send + 'static>(
423 &self,
424 id: Option<window::Id>,
425 maximized: bool,
426 ) -> crate::app::Task<M> {
427 let Some(id) = id.or(self.main_window) else {
428 return iced::Task::none();
429 };
430 crate::command::maximize(id, maximized)
431 }
432
433 #[cfg(feature = "winit")]
434 pub fn minimize<M: Send + 'static>(&self, id: Option<window::Id>) -> crate::app::Task<M> {
435 let Some(id) = id.or(self.main_window) else {
436 return iced::Task::none();
437 };
438 crate::command::minimize(id)
439 }
440
441 #[cfg(feature = "winit")]
442 pub fn set_title<M: Send + 'static>(
443 &self,
444 id: Option<window::Id>,
445 title: String,
446 ) -> crate::app::Task<M> {
447 let Some(id) = id.or(self.main_window) else {
448 return iced::Task::none();
449 };
450 crate::command::set_title(id, title)
451 }
452
453 #[cfg(feature = "winit")]
454 pub fn set_windowed<M: Send + 'static>(&self, id: Option<window::Id>) -> crate::app::Task<M> {
455 let Some(id) = id.or(self.main_window) else {
456 return iced::Task::none();
457 };
458 crate::command::set_windowed(id)
459 }
460
461 #[cfg(feature = "winit")]
462 pub fn toggle_maximize<M: Send + 'static>(
463 &self,
464 id: Option<window::Id>,
465 ) -> crate::app::Task<M> {
466 let Some(id) = id.or(self.main_window) else {
467 return iced::Task::none();
468 };
469
470 crate::command::toggle_maximize(id)
471 }
472}