1pub mod gui;
2
3use crate::gui::{
4 backgrounds::*, characters::*, dialogue::*, game_menu::*, main_menu::*, overlay::*,
5 visual_novel_gui,
6};
7use oxygengine_animation::phase::{Ease, Phase};
8use oxygengine_core::{
9 app::AppBuilder,
10 ecs::{
11 pipeline::{PipelineBuilder, PipelineBuilderError},
12 AccessType, Comp, ResQuery, Universe, WorldRef,
13 },
14 prefab::{Prefab as CorePrefab, PrefabComponent, PrefabManager},
15};
16use oxygengine_user_interface::{
17 component::UserInterfaceView,
18 raui::{core::prelude::*, material::prelude::*},
19 resource::UserInterface,
20};
21use oxygengine_visual_novel::{
22 background::Background, character::Character, dialogue::DialogueOption,
23 resource::VnStoryManager, Position,
24};
25use serde::{Deserialize, Serialize};
26
27pub mod prelude {
28 pub use crate::{
29 gui::{backgrounds::*, characters::*, dialogue::*, game_menu::*, main_menu::*, overlay::*},
30 *,
31 };
32}
33
34fn make_true() -> bool {
35 true
36}
37
38#[derive(Default, Debug, Clone, Serialize, Deserialize)]
40pub struct VisualNovelSyncUserInterface(#[serde(default)] pub String);
41
42impl CorePrefab for VisualNovelSyncUserInterface {}
43impl PrefabComponent for VisualNovelSyncUserInterface {}
44
45pub type ApplyVisualNovelToUserInterfaceSystemResources<'a> = (
46 WorldRef,
47 &'a mut UserInterface,
48 &'a mut VnStoryManager,
49 Comp<&'a VisualNovelSyncUserInterface>,
50 Comp<&'a UserInterfaceView>,
51);
52
53pub fn apply_visual_novel_to_user_interface_system(universe: &mut Universe) {
54 let (world, mut ui, mut manager, ..) =
55 universe.query_resources::<ApplyVisualNovelToUserInterfaceSystemResources>();
56
57 for (_, (sync, view)) in world
58 .query::<(&VisualNovelSyncUserInterface, &UserInterfaceView)>()
59 .iter()
60 {
61 if let Some(data) = ui.get_mut(view.app_id()) {
62 if let Some(story) = manager.get(&sync.0) {
63 if story.is_dirty() {
64 data.application.mark_dirty();
65 }
66 }
67 for (_, msg) in data.signals_received() {
68 if let Some(VisualNovelSignal { story, action }) = msg.as_any().downcast_ref() {
69 if let Some(story) = manager.get_mut(story) {
70 match action {
71 VisualNovelAction::None => {}
72 VisualNovelAction::SelectDialogueOption(None) => {
73 story.unselect_dialogue_option();
74 }
75 VisualNovelAction::FocusDialogueOption(index) => {
76 let _ = story.focus_dialogue_option(*index);
77 }
78 VisualNovelAction::SelectDialogueOption(Some(index)) => {
79 let _ = story.select_dialogue_option(*index);
80 }
81 }
82 }
83 }
84 }
85 }
86 }
87}
88
89#[derive(MessageData, Debug, Clone, Serialize, Deserialize)]
90pub struct VisualNovelSignal {
91 #[serde(default)]
92 pub story: String,
93 #[serde(default)]
94 pub action: VisualNovelAction,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub enum VisualNovelAction {
99 None,
100 FocusDialogueOption(Option<usize>),
101 SelectDialogueOption(Option<usize>),
102}
103
104impl Default for VisualNovelAction {
105 fn default() -> Self {
106 Self::None
107 }
108}
109
110#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
111pub struct VisualNovelStoryUsed(pub String);
112
113#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]
114pub struct VisualNovelOverlayPhase(
115 #[serde(default = "VisualNovelOverlayPhase::default_phase")] pub Phase,
116);
117
118impl Default for VisualNovelOverlayPhase {
119 fn default() -> Self {
120 Self(Self::default_phase())
121 }
122}
123
124impl VisualNovelOverlayPhase {
125 fn default_phase() -> Phase {
126 Phase::ease(Ease::InOutCubic, 0.0..1.0, 0.0..1.0).unwrap()
127 }
128}
129
130#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
131pub struct VisualNovelOverlayMaterial(#[serde(default)] pub ImageBoxMaterial);
132
133#[derive(PropsData, Default, Debug, Copy, Clone, Serialize, Deserialize)]
134pub struct VisualNovelCharacterContentAlignment(pub Option<Position>);
135
136#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]
137pub enum VisualNovelTextTransition {
138 Instant,
139 Fade,
140 Unfold,
141}
142
143impl Default for VisualNovelTextTransition {
144 fn default() -> Self {
145 Self::Fade
146 }
147}
148
149#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
150pub struct VisualNovelDialogueOptionsList(pub VerticalBoxProps);
151
152#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
153pub struct VisualNovelDialogueMessageLayout(pub Option<ContentBoxItemLayout>);
154
155#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
156pub struct VisualNovelDialogueCharacterLayout(pub Option<ContentBoxItemLayout>);
157
158#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
159pub struct VisualNovelDialogueTextLayout(pub Option<ContentBoxItemLayout>);
160
161#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
162pub struct VisualNovelDialogueOptionsLayout(pub Option<ContentBoxItemLayout>);
163
164#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]
165pub struct VisualNovelDialogueMessageThemed {
166 #[serde(default)]
167 pub use_main_color: bool,
168 #[serde(default)]
169 pub color: ThemeColor,
170 #[serde(default)]
171 pub background_variant: Option<String>,
172 #[serde(default)]
173 pub text_variant: Option<String>,
174 #[serde(default = "VisualNovelDialogueMessageThemed::default_margin")]
175 pub margin: Rect,
176}
177
178impl Default for VisualNovelDialogueMessageThemed {
179 fn default() -> Self {
180 Self {
181 use_main_color: false,
182 color: Default::default(),
183 background_variant: None,
184 text_variant: None,
185 margin: Self::default_margin(),
186 }
187 }
188}
189
190impl VisualNovelDialogueMessageThemed {
191 fn default_margin() -> Rect {
192 64.0.into()
193 }
194}
195
196#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]
197pub struct VisualNovelDialogueCharacterThemed {
198 #[serde(default)]
199 pub use_main_color: bool,
200 #[serde(default)]
201 pub color: ThemeColor,
202 #[serde(default)]
203 pub background_variant: Option<String>,
204 #[serde(default)]
205 pub text_variant: Option<String>,
206 #[serde(default = "VisualNovelDialogueCharacterThemed::default_margin")]
207 pub margin: Rect,
208}
209
210impl Default for VisualNovelDialogueCharacterThemed {
211 fn default() -> Self {
212 Self {
213 use_main_color: false,
214 color: Default::default(),
215 margin: Self::default_margin(),
216 background_variant: None,
217 text_variant: None,
218 }
219 }
220}
221
222impl VisualNovelDialogueCharacterThemed {
223 fn default_margin() -> Rect {
224 Rect {
225 left: 32.0,
226 right: 32.0,
227 top: 18.0,
228 bottom: 18.0,
229 }
230 }
231}
232
233#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]
234pub struct VisualNovelDialogueOptionThemed {
235 #[serde(default = "VisualNovelDialogueOptionThemed::default_height")]
236 pub height: SizeBoxSizeValue,
237 #[serde(default)]
238 pub use_main_color: bool,
239 #[serde(default)]
240 pub color_default: ThemeColor,
241 #[serde(default = "VisualNovelDialogueOptionThemed::default_color_focused")]
242 pub color_focused: ThemeColor,
243 #[serde(default)]
244 pub button_variant: Option<String>,
245 #[serde(default)]
246 pub text_variant_default: Option<String>,
247 #[serde(default)]
248 pub text_variant_focused: Option<String>,
249 #[serde(default = "VisualNovelDialogueOptionThemed::default_margin")]
250 pub margin: Rect,
251}
252
253impl Default for VisualNovelDialogueOptionThemed {
254 fn default() -> Self {
255 Self {
256 height: Self::default_height(),
257 use_main_color: false,
258 color_default: Default::default(),
259 color_focused: Self::default_color_focused(),
260 button_variant: None,
261 text_variant_default: None,
262 text_variant_focused: None,
263 margin: Self::default_margin(),
264 }
265 }
266}
267
268impl VisualNovelDialogueOptionThemed {
269 fn default_height() -> SizeBoxSizeValue {
270 SizeBoxSizeValue::Exact(48.0)
271 }
272
273 fn default_color_focused() -> ThemeColor {
274 ThemeColor::Primary
275 }
276
277 fn default_margin() -> Rect {
278 Rect {
279 left: 32.0,
280 right: 32.0,
281 top: 14.0,
282 bottom: 14.0,
283 }
284 }
285}
286
287#[derive(PropsData, Debug, Copy, Clone, Serialize, Deserialize)]
288pub struct VisualNovelGuiProps {
289 #[serde(default = "make_true")]
290 pub overlay: bool,
291 #[serde(default = "make_true")]
292 pub main_menu: bool,
293 #[serde(default = "make_true")]
294 pub game_menu: bool,
295 #[serde(default = "make_true")]
296 pub dialogue: bool,
297 #[serde(default = "make_true")]
298 pub characters: bool,
299 #[serde(default = "make_true")]
300 pub backgrounds: bool,
301}
302
303impl Default for VisualNovelGuiProps {
304 fn default() -> Self {
305 Self {
306 overlay: true,
307 main_menu: true,
308 game_menu: true,
309 dialogue: true,
310 characters: true,
311 backgrounds: true,
312 }
313 }
314}
315
316#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
317pub struct VisualNovelBackgroundsProps {
318 #[serde(default)]
319 pub phase: Scalar,
320 #[serde(default)]
321 pub from: Option<Background>,
322 #[serde(default)]
323 pub to: Option<Background>,
324}
325
326#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
327pub struct VisualNovelCharacterProps(#[serde(default)] pub Character);
328
329#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
330pub struct VisualNovelDialogueMessageProps {
331 #[serde(default)]
332 pub character: String,
333 #[serde(default)]
334 pub text: String,
335 #[serde(default)]
336 pub container_alpha: Scalar,
337 #[serde(default)]
338 pub message_alpha: Scalar,
339}
340
341#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
342pub struct VisualNovelDialogueOptionsProps {
343 #[serde(default)]
344 pub options: Vec<DialogueOption>,
345 #[serde(default)]
346 pub alpha: Scalar,
347}
348
349#[derive(PropsData, Default, Debug, Clone, Serialize, Deserialize)]
350pub struct VisualNovelDialogueOptionProps {
351 #[serde(default)]
352 pub index: usize,
353 #[serde(default)]
354 pub text: String,
355 #[serde(default)]
356 pub alpha: Scalar,
357 #[serde(default)]
358 pub focused: bool,
359 #[serde(default)]
360 pub focus_phase: Scalar,
361}
362
363pub fn ui_setup(app: &mut Application) {
364 app.register_props::<VisualNovelStoryUsed>("VisualNovelStoryUsed");
365 app.register_props::<VisualNovelOverlayMaterial>("VisualNovelOverlayMaterial");
366 app.register_props::<VisualNovelCharacterContentAlignment>(
367 "VisualNovelCharacterContentAlignment",
368 );
369 app.register_props::<VisualNovelTextTransition>("VisualNovelTextTransition");
370 app.register_props::<VisualNovelDialogueOptionsList>("VisualNovelDialogueOptionsList");
371 app.register_props::<VisualNovelDialogueMessageLayout>("VisualNovelDialogueMessageLayout");
372 app.register_props::<VisualNovelDialogueCharacterLayout>("VisualNovelDialogueCharacterLayout");
373 app.register_props::<VisualNovelDialogueTextLayout>("VisualNovelDialogueTextLayout");
374 app.register_props::<VisualNovelDialogueOptionsLayout>("VisualNovelDialogueOptionsLayout");
375 app.register_props::<VisualNovelDialogueMessageThemed>("VisualNovelDialogueMessageThemed");
376 app.register_props::<VisualNovelDialogueCharacterThemed>("VisualNovelDialogueCharacterThemed");
377 app.register_props::<VisualNovelDialogueOptionThemed>("VisualNovelDialogueOptionThemed");
378 app.register_props::<VisualNovelGuiProps>("VisualNovelGuiProps");
379 app.register_props::<VisualNovelBackgroundsProps>("VisualNovelBackgroundsProps");
380 app.register_props::<VisualNovelCharacterProps>("VisualNovelCharacterProps");
381 app.register_props::<VisualNovelDialogueMessageProps>("VisualNovelDialogueMessageProps");
382 app.register_props::<VisualNovelDialogueOptionsProps>("VisualNovelDialogueOptionsProps");
383 app.register_props::<VisualNovelDialogueOptionProps>("VisualNovelDialogueOptionProps");
384
385 app.register_component("visual_novel_gui", visual_novel_gui);
386 app.register_component(
387 "visual_novel_overlay_container",
388 visual_novel_overlay_container,
389 );
390 app.register_component("visual_novel_overlay", visual_novel_overlay);
391 app.register_component("visual_novel_main_menu", visual_novel_main_menu);
392 app.register_component("visual_novel_game_menu", visual_novel_game_menu);
393 app.register_component(
394 "visual_novel_dialogue_container",
395 visual_novel_dialogue_container,
396 );
397 app.register_component(
398 "visual_novel_dialogue_message",
399 visual_novel_dialogue_message,
400 );
401 app.register_component(
402 "visual_novel_dialogue_options",
403 visual_novel_dialogue_options,
404 );
405 app.register_component("visual_novel_dialogue_option", visual_novel_dialogue_option);
406 app.register_component(
407 "visual_novel_characters_container",
408 visual_novel_characters_container,
409 );
410 app.register_component("visual_novel_character", visual_novel_character);
411 app.register_component(
412 "visual_novel_backgrounds_container",
413 visual_novel_backgrounds_container,
414 );
415 app.register_component("visual_novel_backgrounds", visual_novel_backgrounds);
416}
417
418pub fn bundle_installer<PB, Q>(
419 builder: &mut AppBuilder<PB>,
420 _: (),
421) -> Result<(), PipelineBuilderError>
422where
423 PB: PipelineBuilder,
424 Q: AccessType + ResQuery + 'static,
425{
426 builder.install_system::<ApplyVisualNovelToUserInterfaceSystemResources>(
427 "apply-visual-novel-to-user-interface",
428 apply_visual_novel_to_user_interface_system,
429 &["vn-story"],
430 )?;
431 Ok(())
432}
433
434pub fn prefabs_installer(prefabs: &mut PrefabManager) {
435 prefabs
436 .register_component_factory::<VisualNovelSyncUserInterface>("VisualNovelSyncUserInterface");
437}