1use crate::{NiceGuiContext, iced};
2
3use iced::message;
4use iced::program::{self, Program};
5use iced::theme;
6use iced::window;
7use iced::{Element, Executor, Font, Never, Preset, Subscription, Task, Theme};
8
9use iced_debug as debug;
10
11use std::borrow::Cow;
12use std::sync::{Arc, Mutex};
13
14pub mod timed;
15pub use timed::timed;
16
17mod editor_state;
18pub use editor_state::EditorState;
19
20pub fn application<State, EState, Message, Theme, Renderer>(
21 editor_state: EditorState<EState>,
22 nice_ctx: NiceGuiContext,
23 boot: impl BootFn<State, EState, Message>,
24 update: impl UpdateFn<State, Message>,
25 view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
26) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
27where
28 State: 'static,
29 EState: Send + 'static,
30 Message: Send + 'static + message::MaybeDebug + message::MaybeClone,
31 Theme: theme::Base,
32 Renderer: program::Renderer,
33{
34 use std::marker::PhantomData;
35
36 struct Instance<State, EState, Boot, Message, Theme, Renderer, Update, View> {
37 nice_ctx: NiceGuiContext,
38 editor_state: Arc<Mutex<Option<EState>>>,
39 boot: Boot,
40 update: Update,
41 view: View,
42 _state: PhantomData<State>,
43 _message: PhantomData<Message>,
44 _theme: PhantomData<Theme>,
45 _renderer: PhantomData<Renderer>,
46 }
47
48 impl<State, EState, Boot, Message, Theme, Renderer, Update, View> Program
49 for Instance<State, EState, Boot, Message, Theme, Renderer, Update, View>
50 where
51 EState: Send + 'static,
52 Message: Send + 'static,
53 Theme: theme::Base,
54 Renderer: program::Renderer,
55 Boot: self::BootFn<State, EState, Message>,
56 Update: self::UpdateFn<State, Message>,
57 View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
58 {
59 type State = State;
60 type Message = Message;
61 type Theme = Theme;
62 type Renderer = Renderer;
63 type Executor = iced_futures::backend::default::Executor;
64
65 fn name() -> &'static str {
66 let name = std::any::type_name::<State>();
67
68 name.split("::").next().unwrap_or("a_cool_application")
69 }
70
71 fn boot(&self) -> (State, Task<Message>) {
72 let editor_state = EditorState::from_shared(&self.editor_state);
73
74 self.boot.boot(editor_state, self.nice_ctx.clone())
75 }
76
77 fn update(&self, state: &mut Self::State, message: Self::Message) -> Task<Self::Message> {
78 self.update.update(state, message)
79 }
80
81 fn view<'a>(
82 &self,
83 state: &'a Self::State,
84 _window: window::Id,
85 ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
86 self.view.view(state)
87 }
88
89 fn settings(&self) -> iced::Settings {
90 iced::Settings::default()
91 }
92
93 fn window(&self) -> Option<iced::core::window::Settings> {
94 Some(window::Settings::default())
95 }
96 }
97
98 Application {
99 raw: Instance {
100 nice_ctx,
101 editor_state: editor_state.into_shared(),
102 boot,
103 update,
104 view,
105 _state: PhantomData,
106 _message: PhantomData,
107 _theme: PhantomData,
108 _renderer: PhantomData,
109 },
110 iced_settings: iced::Settings::default(),
111 presets: Vec::new(),
112 }
113}
114
115pub struct Application<P: Program> {
123 raw: P,
124 iced_settings: iced::Settings,
125 presets: Vec<Preset<P::State, P::Message>>,
126}
127
128impl<P: Program + Send> Application<P>
129where
130 P::Message: message::MaybeDebug + message::MaybeClone,
131{
132 pub fn run(self) -> impl Program
134 where
135 Self: 'static,
136 {
137 #[cfg(feature = "debug")]
138 iced_debug::init(iced_debug::Metadata {
139 name: P::name(),
140 theme: None,
141 can_time_travel: cfg!(feature = "time-travel"),
142 });
143
144 #[cfg(all(feature = "debug", not(target_arch = "wasm32")))]
145 let program = iced_devtools::attach(ApplicationInner {
146 raw: self.raw,
147 iced_settings: self.iced_settings,
148 presets: self.presets,
149 });
150
151 #[cfg(not(any(all(feature = "debug", not(target_arch = "wasm32")))))]
152 let program = ApplicationInner {
153 raw: self.raw,
154 iced_settings: self.iced_settings,
155 presets: self.presets,
156 };
157
158 program
159 }
160
161 pub fn settings(self, settings: iced::Settings) -> Self {
163 Self {
164 iced_settings: settings,
165 ..self
166 }
167 }
168
169 pub fn antialiasing(self, antialiasing: bool) -> Self {
172 Self {
173 iced_settings: iced::Settings {
174 antialiasing,
175 ..self.iced_settings
176 },
177 ..self
178 }
179 }
180
181 pub fn default_font(self, default_font: Font) -> Self {
183 Self {
184 iced_settings: iced::Settings {
185 default_font,
186 ..self.iced_settings
187 },
188 ..self
189 }
190 }
191
192 pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self {
194 self.iced_settings.fonts.push(font.into());
195 self
196 }
197
198 pub fn title(
200 self,
201 title: impl TitleFn<P::State>,
202 ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
203 Application {
204 raw: program::with_title(self.raw, move |state, _window| title.title(state)),
205 iced_settings: self.iced_settings,
206 presets: self.presets,
207 }
208 }
209
210 pub fn subscription(
212 self,
213 f: impl Fn(&P::State) -> Subscription<P::Message>,
214 ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
215 Application {
216 raw: program::with_subscription(self.raw, f),
217 iced_settings: self.iced_settings,
218 presets: self.presets,
219 }
220 }
221
222 pub fn theme(
224 self,
225 f: impl ThemeFn<P::State, P::Theme>,
226 ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
227 Application {
228 raw: program::with_theme(self.raw, move |state, _window| f.theme(state)),
229 iced_settings: self.iced_settings,
230 presets: self.presets,
231 }
232 }
233
234 pub fn style(
236 self,
237 f: impl Fn(&P::State, &P::Theme) -> theme::Style,
238 ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
239 Application {
240 raw: program::with_style(self.raw, f),
241 iced_settings: self.iced_settings,
242 presets: self.presets,
243 }
244 }
245
246 pub fn scale_factor(
248 self,
249 f: impl Fn(&P::State) -> f32,
250 ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
251 Application {
252 raw: program::with_scale_factor(self.raw, move |state, _window| f(state)),
253 iced_settings: self.iced_settings,
254 presets: self.presets,
255 }
256 }
257
258 pub fn executor<E>(
260 self,
261 ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>>
262 where
263 E: Executor,
264 {
265 Application {
266 raw: program::with_executor::<P, E>(self.raw),
267 iced_settings: self.iced_settings,
268 presets: self.presets,
269 }
270 }
271
272 pub fn presets(self, presets: impl IntoIterator<Item = Preset<P::State, P::Message>>) -> Self {
278 Self {
279 presets: presets.into_iter().collect(),
280 ..self
281 }
282 }
283}
284
285pub(crate) struct ApplicationInner<P: Program> {
293 raw: P,
294 iced_settings: iced::Settings,
295 presets: Vec<Preset<P::State, P::Message>>,
296}
297
298impl<P: Program> Program for ApplicationInner<P> {
299 type State = P::State;
300 type Message = P::Message;
301 type Theme = P::Theme;
302 type Renderer = P::Renderer;
303 type Executor = P::Executor;
304
305 fn name() -> &'static str {
306 P::name()
307 }
308
309 fn settings(&self) -> iced::Settings {
310 self.iced_settings.clone()
311 }
312
313 fn window(&self) -> Option<window::Settings> {
314 Some(window::Settings::default())
316 }
317
318 fn boot(&self) -> (Self::State, Task<Self::Message>) {
319 self.raw.boot()
320 }
321
322 fn update(&self, state: &mut Self::State, message: Self::Message) -> Task<Self::Message> {
323 debug::hot(|| self.raw.update(state, message))
324 }
325
326 fn view<'a>(
327 &self,
328 state: &'a Self::State,
329 window: window::Id,
330 ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
331 debug::hot(|| self.raw.view(state, window))
332 }
333
334 fn title(&self, state: &Self::State, window: window::Id) -> String {
335 debug::hot(|| self.raw.title(state, window))
336 }
337
338 fn subscription(&self, state: &Self::State) -> Subscription<Self::Message> {
339 debug::hot(|| self.raw.subscription(state))
340 }
341
342 fn theme(&self, state: &Self::State, window: iced::window::Id) -> Option<Self::Theme> {
343 debug::hot(|| self.raw.theme(state, window))
344 }
345
346 fn style(&self, state: &Self::State, theme: &Self::Theme) -> theme::Style {
347 debug::hot(|| self.raw.style(state, theme))
348 }
349
350 fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
351 debug::hot(|| self.raw.scale_factor(state, window))
352 }
353
354 fn presets(&self) -> &[Preset<Self::State, Self::Message>] {
355 &self.presets
356 }
357}
358
359pub trait BootFn<State, EState: Send + 'static, Message> {
368 fn boot(
370 &self,
371 editor_state: EditorState<EState>,
372 nice_ctx: NiceGuiContext,
373 ) -> (State, Task<Message>);
374}
375
376impl<T, C, State, EState: Send + 'static, Message> BootFn<State, EState, Message> for T
377where
378 T: Fn(EditorState<EState>, NiceGuiContext) -> C,
379 C: IntoBoot<State, Message>,
380{
381 fn boot(
382 &self,
383 editor_state: EditorState<EState>,
384 nice_ctx: NiceGuiContext,
385 ) -> (State, Task<Message>) {
386 self(editor_state, nice_ctx).into_boot()
387 }
388}
389
390pub trait IntoBoot<State, Message> {
392 fn into_boot(self) -> (State, Task<Message>);
394}
395
396impl<State, Message> IntoBoot<State, Message> for State {
397 fn into_boot(self) -> (State, Task<Message>) {
398 (self, Task::none())
399 }
400}
401
402impl<State, Message> IntoBoot<State, Message> for (State, Task<Message>) {
403 fn into_boot(self) -> (State, Task<Message>) {
404 self
405 }
406}
407
408pub trait TitleFn<State> {
415 fn title(&self, state: &State) -> String;
417}
418
419impl<State> TitleFn<State> for &'static str {
420 fn title(&self, _state: &State) -> String {
421 self.to_string()
422 }
423}
424
425impl<T, State> TitleFn<State> for T
426where
427 T: Fn(&State) -> String,
428{
429 fn title(&self, state: &State) -> String {
430 self(state)
431 }
432}
433
434pub trait UpdateFn<State, Message> {
439 fn update(&self, state: &mut State, message: Message) -> Task<Message>;
441}
442
443impl<State> UpdateFn<State, Never> for () {
444 fn update(&self, _state: &mut State, _message: Never) -> Task<Never> {
445 Task::none()
446 }
447}
448
449impl<T, State, Message, C> UpdateFn<State, Message> for T
450where
451 T: Fn(&mut State, Message) -> C,
452 C: Into<Task<Message>>,
453{
454 fn update(&self, state: &mut State, message: Message) -> Task<Message> {
455 self(state, message).into()
456 }
457}
458
459pub trait ViewFn<'a, State, Message, Theme, Renderer> {
464 fn view(&self, state: &'a State) -> Element<'a, Message, Theme, Renderer>;
466}
467
468impl<'a, T, State, Message, Theme, Renderer, Widget> ViewFn<'a, State, Message, Theme, Renderer>
469 for T
470where
471 T: Fn(&'a State) -> Widget,
472 State: 'static,
473 Widget: Into<Element<'a, Message, Theme, Renderer>>,
474{
475 fn view(&self, state: &'a State) -> Element<'a, Message, Theme, Renderer> {
476 self(state).into()
477 }
478}
479
480pub trait ThemeFn<State, Theme> {
489 fn theme(&self, state: &State) -> Option<Theme>;
494}
495
496impl<State> ThemeFn<State, Theme> for Theme {
497 fn theme(&self, _state: &State) -> Option<Theme> {
498 Some(self.clone())
499 }
500}
501
502impl<F, T, State, Theme> ThemeFn<State, Theme> for F
503where
504 F: Fn(&State) -> T,
505 T: Into<Option<Theme>>,
506{
507 fn theme(&self, state: &State) -> Option<Theme> {
508 (self)(state).into()
509 }
510}