1use alloc::format;
23use alloc::string::String;
24use alloc::vec::Vec;
25
26use base::Ini;
27
28use super::consts::*;
29use super::layout::px2dip_at;
30use super::Ui;
31
32const SEC_MAIN: &str = "window";
33const SEC_PREFIX: &str = "window ";
34const SIZE_MAX_DIP: i32 = 8192;
35const POS_MAX: i32 = 1 << 20;
36const VISIBLE_MIN: i32 = 48; #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
40pub(crate) struct MainState {
41 pub(crate) x: i32,
42 pub(crate) y: i32,
43 pub(crate) pos: bool,
44 pub(crate) w: i32,
45 pub(crate) h: i32,
46 pub(crate) max: bool,
47}
48
49#[derive(Clone, Copy, Debug)]
51pub(crate) struct Saved {
52 id: u32,
53 open: i32, rect: Option<WinRect>,
55 open_done: bool, }
57
58fn keepable(flags: u32) -> bool {
60 flags & WF_TOOL != 0 && flags & (WF_DIALOG | WF_MODAL) == 0
61}
62
63fn int(ini: &Ini, sec: &str, key: &str) -> Option<i64> {
64 let v = ini.get_int(sec, key, i64::MIN);
65 (v != i64::MIN).then_some(v)
66}
67
68pub(crate) fn main_saved(ini: &Ini) -> Option<MainState> {
70 let w = int(ini, SEC_MAIN, "w")?;
71 let h = int(ini, SEC_MAIN, "h")?;
72 if !(1..=SIZE_MAX_DIP as i64).contains(&w) || !(1..=SIZE_MAX_DIP as i64).contains(&h) {
73 return None;
74 }
75 let (x, y) = (int(ini, SEC_MAIN, "x"), int(ini, SEC_MAIN, "y"));
76 let pos = matches!((x, y), (Some(x), Some(y)) if x.abs() < POS_MAX as i64 && y.abs() < POS_MAX as i64);
77 Some(MainState {
78 x: if pos { x.unwrap_or(0) as i32 } else { 0 },
79 y: if pos { y.unwrap_or(0) as i32 } else { 0 },
80 pos,
81 w: w as i32,
82 h: h as i32,
83 max: int(ini, SEC_MAIN, "max").unwrap_or(0) != 0,
84 })
85}
86
87pub(crate) fn state_file(flags: u32) -> Option<String> {
90 match plat::env("jfgui_STATE") {
91 Some(v) if v == "0" => None,
92 Some(v) if !v.is_empty() && v != "1" => Some(v),
93 Some(_) => Some(String::from("config://windows.ini")),
94 None => (flags & AF_SAVE_STATE != 0).then(|| String::from("config://windows.ini")),
95 }
96}
97
98pub(crate) fn fit_area(x: i32, y: i32, w: i32, h: i32, area: [i32; 4]) -> (i32, i32) {
101 let [ax, ay, aw, ah] = area;
102 if aw <= 0 || ah <= 0 {
103 return (x, y);
104 }
105 let seen_x = (x + w).min(ax + aw) - x.max(ax);
106 if seen_x >= VISIBLE_MIN.min(w) && y >= ay && y <= ay + ah - VISIBLE_MIN {
107 return (x, y);
108 }
109 (ax + ((aw - w) / 2).max(0), ay + ((ah - h) / 2).max(VISIBLE_MIN.min(ah / 8)))
110}
111
112impl Ui {
113 pub fn state_save(&mut self, ini: &mut Ini) {
116 if let Some(m) = self.ws_main_now() {
117 if m.pos {
118 ini.set_int(SEC_MAIN, "x", m.x as i64);
119 ini.set_int(SEC_MAIN, "y", m.y as i64);
120 } else {
121 ini.remove(SEC_MAIN, "x");
122 ini.remove(SEC_MAIN, "y");
123 }
124 ini.set_int(SEC_MAIN, "w", m.w as i64);
125 ini.set_int(SEC_MAIN, "h", m.h as i64);
126 ini.set_int(SEC_MAIN, "max", m.max as i64);
127 }
128 for i in 1..WIN_MAX {
129 let r = &self.m.win.recs[i];
130 if r.id == 0 || !keepable(r.flags) || r.id == super::inspect::INSPECT_WIN {
131 continue; }
133 let sec = format!("{}{}", SEC_PREFIX, r.id);
134 ini.set_int(&sec, "open", (r.state & super::win::UWI_OPEN != 0) as i64);
135 if r.state & super::win::UWI_SHOWN != 0 {
136 let rc = r.rect.get();
137 ini.set_int(&sec, "x", rc.x as i64);
138 ini.set_int(&sec, "y", rc.y as i64);
139 ini.set_int(&sec, "w", rc.w as i64);
140 ini.set_int(&sec, "h", rc.h as i64);
141 }
142 }
143 }
144
145 pub fn state_load(&mut self, ini: &Ini) {
148 if let Some(m) = main_saved(ini) {
149 let mut flags = 0;
150 if m.pos && !self.headless {
151 flags |= WRF_POS | WRF_ABS;
152 }
153 if m.max && !self.headless {
154 flags |= WRF_MAX;
155 }
156 self.window_place(0, &WinRect::new(m.x, m.y, m.w, m.h, flags));
157 self.m.win.norm = m;
158 }
159 self.ws_windows(ini);
160 }
161
162 pub(crate) fn ws_windows(&mut self, ini: &Ini) {
164 let mut saved = Vec::new();
165 for sec in ini.sections() {
166 let Some(id) = sec.strip_prefix(SEC_PREFIX).and_then(|n| n.trim().parse::<u32>().ok()) else { continue };
167 if id == 0 {
168 continue;
169 }
170 let open = int(ini, sec, "open").map_or(-1, |v| (v != 0) as i32);
171 let rect = match (int(ini, sec, "x"), int(ini, sec, "y"), int(ini, sec, "w"), int(ini, sec, "h")) {
172 (Some(x), Some(y), Some(w), Some(h)) if x.abs() < POS_MAX as i64 && y.abs() < POS_MAX as i64 => Some(WinRect::new(
173 x as i32,
174 y as i32,
175 w.clamp(1, SIZE_MAX_DIP as i64) as i32,
176 h.clamp(1, SIZE_MAX_DIP as i64) as i32,
177 WRF_POS,
178 )),
179 _ => None,
180 };
181 saved.retain(|s: &Saved| s.id != id);
182 saved.push(Saved { id, open, rect, open_done: false });
183 }
184 self.m.win.saved = saved;
185 }
186
187 pub(crate) fn ws_open(&mut self, id: u32, flags: u32, open: Option<&super::Shared<i32>>) {
190 let Some(k) = self.m.win.saved.iter().position(|s| s.id == id) else { return };
191 if !keepable(flags) {
192 self.m.win.saved.remove(k);
193 return;
194 }
195 let s = &mut self.m.win.saved[k];
196 if s.open_done {
197 return;
198 }
199 s.open_done = true;
200 if let (Some(o), true) = (open, s.open >= 0) {
201 o.set(s.open);
202 }
203 }
204
205 pub(crate) fn ws_rect(&mut self, i: u32, id: u32) {
207 let Some(k) = self.m.win.saved.iter().position(|s| s.id == id) else { return };
208 let s = self.m.win.saved.remove(k);
209 let Some(src) = s.rect else { return };
210 let r = &mut self.m.win.recs[i as usize & (WIN_MAX - 1)];
211 let rc = r.rect.get();
212 r.rect.set(WinRect { x: src.x, y: src.y, w: src.w, h: src.h, flags: (rc.flags & !(WRF_CHANGED | WRF_MAX)) | WRF_POS });
213 r.state &= !super::win::UWI_DEFPOS;
214 }
215
216 pub(crate) fn ws_track(&mut self) {
221 if self.headless {
222 return;
223 }
224 let big = self.ws_big();
225 self.ws_commit(big);
226 if !big {
227 self.m.win.cand = self.ws_main_live();
228 }
229 }
230
231 fn ws_big(&self) -> bool {
233 self.uiw_plat_state(0) & (plat::WST_MAXIMIZED | plat::WST_MINIMIZED | plat::WST_FULLSCREEN) != 0
234 }
235
236 fn ws_commit(&mut self, big: bool) {
238 if let Some(c) = self.m.win.cand.take() {
239 if !big || self.ws_main_live().is_none_or(|l| (l.w, l.h) != (c.w, c.h)) {
240 self.m.win.norm = c;
241 }
242 }
243 }
244
245 fn ws_main_live(&self) -> Option<MainState> {
247 let fb = self.win_fb(0);
248 let dpi = self.win_dpi(0);
249 if fb.w <= 0 || fb.h <= 0 {
250 return None;
251 }
252 let rc = self.m.win.recs[0].rect.get();
255 let at = if self.headless {
256 None
257 } else if let Some(r) = plat::win_get_rect(self.uiw_pw(0)) {
258 Some((r[0], r[1]))
259 } else if rc.flags & WRF_POS != 0 {
260 Some((rc.x, rc.y))
261 } else if self.m.win.norm.pos {
262 Some((self.m.win.norm.x, self.m.win.norm.y))
263 } else {
264 None
265 };
266 Some(MainState {
267 x: at.map_or(0, |a| a.0),
268 y: at.map_or(0, |a| a.1),
269 pos: at.is_some(),
270 w: px2dip_at(fb.w, dpi),
271 h: px2dip_at(fb.h, dpi),
272 max: false,
273 })
274 }
275
276 fn ws_main_now(&mut self) -> Option<MainState> {
279 if self.headless {
280 return self.ws_main_live();
281 }
282 let big = self.ws_big();
283 self.ws_commit(big);
284 let max = self.uiw_plat_state(0) & plat::WST_MAXIMIZED != 0;
285 let mut m = if big { self.m.win.norm } else { self.ws_main_live().unwrap_or(self.m.win.norm) };
286 if m.w <= 0 || m.h <= 0 {
287 return None;
288 }
289 m.max = max;
290 Some(m)
291 }
292}