1use std::cell::RefCell;
6use std::panic::{self, AssertUnwindSafe};
7use std::rc::Rc;
8
9use crabshell_reactive::{ReadSignal, Signal, untrack};
10
11use crate::cx::{Anchor, Cx};
12use crate::input::panic_message;
13use crate::prop::{IntoProp, Prop, PropKind};
14use crate::style::Style;
15use crate::tree::{Tree, borrow};
16use crate::widgets::Widget;
17use crate::window::{Output, Window};
18
19type Build = Rc<dyn Fn(&Cx, &Output) -> Box<dyn Widget>>;
21
22type Parts = (Option<Rc<dyn Fn() -> Style>>, Option<Build>);
24
25#[derive(Default)]
39pub struct SessionLock {
40 locked: Option<Prop<bool>>,
41 style: Option<Prop<Style>>,
42 surface: Option<Build>,
43}
44
45impl SessionLock {
46 pub fn new() -> SessionLock {
48 SessionLock::default()
49 }
50
51 pub fn locked(mut self, locked: impl IntoProp<bool>) -> SessionLock {
54 self.locked = Some(locked.into_prop());
55 self
56 }
57
58 pub fn style(mut self, style: impl IntoProp<Style>) -> SessionLock {
62 self.style = Some(style.into_prop());
63 self
64 }
65
66 pub fn surface<W: Widget>(mut self, build: impl Fn(&Cx, &Output) -> W + 'static) -> SessionLock {
70 self.surface = Some(Rc::new(move |cx: &Cx, output: &Output| {
71 Box::new(build(cx, output)) as Box<dyn Widget>
72 }));
73 self
74 }
75}
76
77pub struct Lock {
82 cx: Cx,
83 wanted: Signal<bool>,
85 locked: Signal<bool>,
87 parts: Signal<Parts>,
90}
91
92impl Lock {
93 pub fn new(cx: &Cx, lock: SessionLock) -> Lock {
100 let cx = cx.child().named("SessionLock");
101 let wanted = cx.signal(false);
102 match lock.locked.map(|locked| locked.0) {
103 None => {}
104 Some(PropKind::Value(value)) => wanted.set(value),
105 Some(PropKind::Binding(mut compute)) => {
106 cx.effect(move || wanted.set(compute()))
107 .named("SessionLock.locked");
108 }
109 }
110 Lock {
111 wanted,
112 locked: cx.signal(false),
113 parts: cx.signal((lock.style.map(Prop::shared), lock.surface)),
114 cx,
115 }
116 }
117
118 pub fn wanted(&self) -> Option<bool> {
120 untrack(|| self.wanted.try_get())
121 }
122
123 pub fn locked(&self) -> ReadSignal<bool> {
125 self.locked.read_only()
126 }
127
128 pub fn set_locked(&self, locked: bool) {
131 if untrack(|| self.locked.try_get()) == Some(!locked) {
132 self.locked.set(locked);
133 }
134 }
135
136 pub fn surface(&self, output: &Output) -> Option<Window> {
141 let (style, build) = untrack(|| self.parts.try_with(Clone::clone))?;
142 let path: Rc<str> = format!("SessionLock[{}]", output.name()).into();
143 let cx = self
144 .cx
145 .child()
146 .named(path.to_string())
147 .at(Rc::new(Anchor::default()));
148 let built = panic::catch_unwind(AssertUnwindSafe(|| {
150 untrack(|| build.map(|build| build(&cx, output).into_element()))
151 }));
152 let child = built.unwrap_or_else(|payload| {
153 tracing::error!(
154 "{path} panicked while it was built, so it shows only the lock's style: {}",
155 panic_message(payload.as_ref())
156 );
157 None
158 });
159 let owner = cx.owner();
160 let tree = Rc::new_cyclic(|this| {
161 RefCell::new(Tree::new(
162 this.clone(),
163 "SessionLock",
164 output.clone(),
165 cx,
166 path,
167 ))
168 });
169 let style = style.map(|style| (move || style()).into_prop());
170 borrow(&tree).mount_root(style, child);
171 Some(Window { tree, owner })
172 }
173}
174
175impl Drop for Lock {
176 fn drop(&mut self) {
177 self.cx.owner().dispose();
178 }
179}