veloren_voxygen/ui/mod.rs
1mod cache;
2mod event;
3pub mod graphic;
4mod scale;
5mod widgets;
6#[macro_use]
7pub mod img_ids;
8#[macro_use]
9pub mod fonts;
10#[cfg(feature = "egui-ui")] pub mod egui;
11pub mod ice;
12pub mod keyed_jobs;
13
14pub use event::Event;
15pub use graphic::{Graphic, Id as GraphicId, Rotation, SampleStrat, Transform};
16pub use keyed_jobs::KeyedJobs;
17pub use scale::{Scale, ScaleMode};
18pub use widgets::{
19 image_frame::ImageFrame,
20 image_slider::ImageSlider,
21 ingame::{Ingame, Ingameable},
22 item_tooltip::{ItemTooltip, ItemTooltipManager, ItemTooltipable},
23 outlined_text::OutlinedText,
24 radio_list::RadioList,
25 rich_text::RichText,
26 slot,
27 toggle_button::ToggleButton,
28 tooltip::{Tooltip, TooltipManager, Tooltipable},
29};
30
31use crate::{
32 error::Error,
33 render::{
34 DynamicModel, Mesh, RenderError, Renderer, UiBoundLocals, UiDrawer, UiLocals, UiMode,
35 UiVertex, create_ui_quad, create_ui_tri,
36 },
37 window::Window,
38};
39#[rustfmt::skip]
40use ::image::GenericImageView;
41use cache::Cache;
42use common::{slowjob::SlowJobPool, util::srgba_to_linear};
43use common_base::span;
44use conrod_core::{
45 Rect, Scalar, UiBuilder, UiCell,
46 event::Input,
47 graph::{self, Graph},
48 image::{Id as ImageId, Map},
49 input::{Motion, Widget, touch::Touch},
50 render::{Primitive, PrimitiveKind},
51 text::{self, font},
52 widget::{self, id::Generator},
53};
54use core::{convert::TryInto, f64, ops::Range};
55use graphic::TexId;
56use hashbrown::hash_map::Entry;
57use std::time::Duration;
58use tracing::{error, warn};
59use vek::*;
60
61#[derive(Debug)]
62pub enum UiError {
63 RenderError(RenderError),
64}
65
66enum DrawKind {
67 Image(TexId),
68 // Text and non-textured geometry
69 Plain,
70}
71
72enum DrawCommand {
73 Draw { kind: DrawKind, verts: Range<u32> },
74 Scissor(Aabr<u16>),
75 WorldPos(Option<usize>),
76}
77impl DrawCommand {
78 fn image(verts: Range<usize>, id: TexId) -> DrawCommand {
79 DrawCommand::Draw {
80 kind: DrawKind::Image(id),
81 verts: verts
82 .start
83 .try_into()
84 .expect("Vertex count for UI rendering does not fit in a u32!")
85 ..verts
86 .end
87 .try_into()
88 .expect("Vertex count for UI rendering does not fit in a u32!"),
89 }
90 }
91
92 fn plain(verts: Range<usize>) -> DrawCommand {
93 DrawCommand::Draw {
94 kind: DrawKind::Plain,
95 verts: verts
96 .start
97 .try_into()
98 .expect("Vertex count for UI rendering does not fit in a u32!")
99 ..verts
100 .end
101 .try_into()
102 .expect("Vertex count for UI rendering does not fit in a u32!"),
103 }
104 }
105}
106
107pub struct Ui {
108 pub ui: conrod_core::Ui,
109 image_map: Map<(graphic::Id, Rotation)>,
110 cache: Cache,
111 // Draw commands for the next render
112 draw_commands: Vec<DrawCommand>,
113 // Mesh buffer for UI vertices; we reuse its allocation in order to limit vector reallocations
114 // during redrawing.
115 mesh: Mesh<UiVertex>,
116 // Model for drawing the ui
117 model: DynamicModel<UiVertex>,
118 // Consts for default ui drawing position (ie the interface)
119 interface_locals: UiBoundLocals,
120 // Consts to specify positions of ingame elements (e.g. Nametags)
121 ingame_locals: Vec<UiBoundLocals>,
122 // Whether the window was resized since the last maintain, for updating scaling
123 window_resized: bool,
124 // Scale factor changed
125 scale_factor_changed: Option<f64>,
126 // Used to delay cache resizing until after current frame is drawn
127 need_cache_resize: bool,
128 // Whether a graphic was replaced with replaced_graphic since the last maintain call
129 graphic_replaced: bool,
130 // Scaling of the ui
131 scale: Scale,
132 // Tooltips
133 tooltip_manager: TooltipManager,
134 // Item tooltips manager
135 item_tooltip_manager: ItemTooltipManager,
136 // Scissor for the whole window
137 window_scissor: Aabr<u16>,
138}
139
140impl Ui {
141 pub fn new(window: &mut Window) -> Result<Self, Error> {
142 let scale_factor = window.scale_factor();
143 let renderer = window.renderer_mut();
144 let physical_resolution = renderer.resolution();
145 let scale = Scale::new(
146 physical_resolution,
147 scale_factor,
148 ScaleMode::Absolute(1.0),
149 1.0,
150 );
151
152 let win_dims = scale.scaled_resolution().into_array();
153
154 let mut ui = UiBuilder::new(win_dims).build();
155 // NOTE: Since we redraw the actual frame each time whether or not the UI needs
156 // to be updated, there's no reason to set the redraw count higher than
157 // 1.
158 ui.set_num_redraw_frames(1);
159
160 let item_tooltip_manager = ItemTooltipManager::new(
161 Duration::from_millis(1),
162 Duration::from_millis(0),
163 scale.scale_factor_logical(),
164 );
165
166 let tooltip_manager = TooltipManager::new(
167 ui.widget_id_generator(),
168 Duration::from_millis(1),
169 Duration::from_millis(0),
170 scale.scale_factor_logical(),
171 );
172
173 let interface_locals = renderer.create_ui_bound_locals(&[UiLocals::default()]);
174
175 Ok(Self {
176 ui,
177 image_map: Map::new(),
178 cache: Cache::new(renderer)?,
179 draw_commands: Vec::new(),
180 mesh: Mesh::new(),
181 model: renderer.create_dynamic_model(100),
182 interface_locals,
183 ingame_locals: Vec::new(),
184 window_resized: false,
185 scale_factor_changed: None,
186 need_cache_resize: false,
187 graphic_replaced: false,
188 scale,
189 tooltip_manager,
190 item_tooltip_manager,
191 window_scissor: default_scissor(physical_resolution),
192 })
193 }
194
195 // Set the scaling mode of the ui.
196 pub fn set_scaling_mode(&mut self, mode: ScaleMode) {
197 self.scale.set_scaling_mode(mode);
198 // To clear the cache (it won't be resized in this case)
199 self.need_cache_resize = true;
200 // Give conrod the new size.
201 let (w, h) = self.scale.scaled_resolution().into_tuple();
202 self.ui.handle_event(Input::Resize(w, h));
203 }
204
205 pub fn scale_factor_changed(&mut self, scale_factor: f64) {
206 self.scale_factor_changed = Some(scale_factor);
207 }
208
209 // Get a copy of Scale
210 pub fn scale(&self) -> Scale { self.scale }
211
212 pub fn add_graphic(&mut self, graphic: Graphic) -> ImageId {
213 self.image_map
214 .insert((self.cache.add_graphic(graphic), Rotation::None))
215 }
216
217 pub fn add_graphic_with_rotations(&mut self, graphic: Graphic) -> img_ids::Rotations {
218 let graphic_id = self.cache.add_graphic(graphic);
219 img_ids::Rotations {
220 none: self.image_map.insert((graphic_id, Rotation::None)),
221 cw90: self.image_map.insert((graphic_id, Rotation::Cw90)),
222 cw180: self.image_map.insert((graphic_id, Rotation::Cw180)),
223 cw270: self.image_map.insert((graphic_id, Rotation::Cw270)),
224 // Hacky way to make sure a source rectangle always faces north regardless of player
225 // orientation.
226 // This is an easy way to get around Conrod's lack of rotation data for images (for this
227 // specific use case).
228 source_north: self.image_map.insert((graphic_id, Rotation::SourceNorth)),
229 // Hacky way to make sure a target rectangle always faces north regardless of player
230 // orientation.
231 // This is an easy way to get around Conrod's lack of rotation data for images (for this
232 // specific use case).
233 target_north: self.image_map.insert((graphic_id, Rotation::TargetNorth)),
234 // rotates rectangle to always point in the characters orientation adjusted by the
235 // minimaps rotation (if the minimap is being rotated based on camera position).
236 target_player: self.image_map.insert((graphic_id, Rotation::TargetPlayer)),
237 }
238 }
239
240 pub fn replace_graphic(&mut self, id: ImageId, graphic: Graphic) {
241 if let Some(&(graphic_id, _)) = self.image_map.get(&id) {
242 self.cache.replace_graphic(graphic_id, graphic);
243 self.image_map.replace(id, (graphic_id, Rotation::None));
244 self.graphic_replaced = true;
245 } else {
246 error!("Failed to replace graphic, the provided id is not in use.");
247 };
248 }
249
250 pub fn new_font(&mut self, font: ice::RawFont) -> font::Id {
251 let font = text::Font::from_bytes(font.0).unwrap();
252 self.ui.fonts.insert(font)
253 }
254
255 pub fn id_generator(&mut self) -> Generator<'_> { self.ui.widget_id_generator() }
256
257 pub fn set_widgets(&mut self) -> (UiCell<'_>, &mut ItemTooltipManager, &mut TooltipManager) {
258 (
259 self.ui.set_widgets(),
260 &mut self.item_tooltip_manager,
261 &mut self.tooltip_manager,
262 )
263 }
264
265 pub fn set_item_widgets(&mut self) -> (UiCell<'_>, &mut ItemTooltipManager) {
266 (self.ui.set_widgets(), &mut self.item_tooltip_manager)
267 }
268
269 // Accepts Option so widget can be unfocused.
270 pub fn focus_widget(&mut self, id: Option<widget::Id>) {
271 self.ui.keyboard_capture(match id {
272 Some(id) => id,
273 None => self.ui.window,
274 });
275 }
276
277 // Get id of current widget capturing keyboard.
278 pub fn widget_capturing_keyboard(&self) -> Option<widget::Id> {
279 self.ui.global_input().current.widget_capturing_keyboard
280 }
281
282 // Get whether a widget besides the window is capturing the mouse.
283 pub fn no_widget_capturing_mouse(&self) -> bool {
284 self.ui
285 .global_input()
286 .current
287 .widget_capturing_mouse
288 .filter(|id| id != &self.ui.window)
289 .is_none()
290 }
291
292 // Get the widget graph.
293 pub fn widget_graph(&self) -> &Graph { self.ui.widget_graph() }
294
295 pub fn handle_event(&mut self, event: Event) {
296 match event.0 {
297 Input::Resize(w, h) => {
298 if w > 0.0 && h > 0.0 {
299 self.window_resized = true;
300 }
301 },
302 Input::Touch(touch) => self.ui.handle_event(Input::Touch(Touch {
303 xy: self.scale.scale_point(touch.xy.into()).into_array(),
304 ..touch
305 })),
306 Input::Motion(motion) => self.ui.handle_event(Input::Motion(match motion {
307 Motion::MouseCursor { x, y } => {
308 let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
309 Motion::MouseCursor { x, y }
310 },
311 Motion::MouseRelative { x, y } => {
312 let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
313 Motion::MouseRelative { x, y }
314 },
315 Motion::Scroll { x, y } => {
316 let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
317 Motion::Scroll { x, y }
318 },
319 _ => motion,
320 })),
321 _ => self.ui.handle_event(event.0),
322 }
323 }
324
325 pub fn widget_input(&self, id: widget::Id) -> Widget<'_> { self.ui.widget_input(id) }
326
327 pub fn maintain(
328 &mut self,
329 renderer: &mut Renderer,
330 pool: Option<&SlowJobPool>,
331 view_projection_mat: Option<Mat4<f32>>,
332 ) {
333 span!(_guard, "maintain", "Ui::maintain");
334 // Maintain tooltip manager
335 self.tooltip_manager
336 .maintain(self.ui.global_input(), self.scale.scale_factor_logical());
337
338 // Maintain tooltip manager
339 self.item_tooltip_manager
340 .maintain(self.ui.global_input(), self.scale.scale_factor_logical());
341
342 // Handle scale factor changing
343 let need_resize = if let Some(scale_factor) = self.scale_factor_changed.take() {
344 self.scale.scale_factor_changed(scale_factor)
345 } else {
346 false
347 };
348
349 // Used to tell if we need to clear out the draw commands (which contain scissor
350 // commands that can be invalidated by this change)
351 let physical_resolution_changed = renderer.resolution() != self.scale.physical_resolution();
352
353 // Handle window resizing.
354 let need_resize = if self.window_resized {
355 self.window_resized = false;
356 let surface_resolution = renderer.resolution();
357 let (old_w, old_h) = self.scale.scaled_resolution().into_tuple();
358 self.scale.surface_resized(surface_resolution);
359 let (w, h) = self.scale.scaled_resolution().into_tuple();
360 self.ui.handle_event(Input::Resize(w, h));
361 self.window_scissor = default_scissor(surface_resolution);
362
363 // Avoid panic in graphic cache when minimizing.
364 // Avoid resetting cache if window size didn't change
365 // Somewhat inefficient for elements that won't change size after a window
366 // resize
367 let res = renderer.resolution();
368 res.x > 0 && res.y > 0 && !(old_w == w && old_h == h)
369 } else {
370 false
371 } || need_resize;
372
373 if need_resize {
374 self.need_cache_resize = true;
375 }
376
377 if self.need_cache_resize {
378 // Resize graphic cache
379 // FIXME: Handle errors here.
380 self.cache.resize(renderer).unwrap();
381
382 self.need_cache_resize = false;
383 }
384
385 let mut retry = false;
386 self.maintain_internal(
387 renderer,
388 pool,
389 view_projection_mat,
390 &mut retry,
391 physical_resolution_changed,
392 );
393 if retry {
394 // Update the glyph cache and try again.
395 self.maintain_internal(renderer, pool, view_projection_mat, &mut retry, false);
396 }
397 }
398
399 fn maintain_internal(
400 &mut self,
401 renderer: &mut Renderer,
402 pool: Option<&SlowJobPool>,
403 view_projection_mat: Option<Mat4<f32>>,
404 retry: &mut bool,
405 physical_resolution_changed: bool,
406 ) {
407 span!(_guard, "internal", "Ui::maintain_internal");
408 let (graphic_cache, text_cache, glyph_cache, cache_tex) = self.cache.cache_mut_and_tex();
409
410 // If the physical resolution changed draw commands need to be cleared since
411 // scissors commands will be invalid. A resize usually means everything
412 // needs to be redrawn anyway but certain error cases below can cause an
413 // early return.
414 if physical_resolution_changed {
415 self.draw_commands.clear();
416 }
417
418 let mut primitives = if *retry || self.graphic_replaced || physical_resolution_changed {
419 // If this is a retry, always redraw.
420 //
421 // Also redraw if a texture was swapped out by replace_graphic in order to
422 // regenerate invalidated textures and clear out any invalid `TexId`s.
423 //
424 // Also redraw if the physical resolution changed since we need to regenerate
425 // the invalid scissor rect commands.
426 self.graphic_replaced = false;
427 self.ui.draw()
428 } else {
429 // Otherwise, redraw only if widgets were actually updated.
430 match self.ui.draw_if_changed() {
431 Some(primitives) => primitives,
432 None => return,
433 }
434 };
435
436 let (half_res, x_align, y_align) = {
437 let res = renderer.resolution();
438 (
439 res.map(|e| e as f32 / 2.0),
440 (res.x & 1) as f32 * 0.5,
441 (res.y & 1) as f32 * 0.5,
442 )
443 };
444
445 let ui = &self.ui;
446 let p_scale_factor = self.scale.scale_factor_physical();
447 // Functions for converting for conrod scalar coords to GL vertex coords (-1.0
448 // to 1.0).
449 let (ui_win_w, ui_win_h) = (ui.win_w, ui.win_h);
450 let vx = |x: f64| (x / ui_win_w * 2.0) as f32;
451 let vy = |y: f64| (y / ui_win_h * 2.0) as f32;
452 let gl_aabr = |rect: Rect| {
453 let (l, r, b, t) = rect.l_r_b_t();
454 let min = Vec2::new(
455 ((vx(l) * half_res.x + x_align).round() - x_align) / half_res.x,
456 ((vy(b) * half_res.y + y_align).round() - y_align) / half_res.y,
457 );
458 let max = Vec2::new(
459 ((vx(r) * half_res.x + x_align).round() - x_align) / half_res.x,
460 ((vy(t) * half_res.y + y_align).round() - y_align) / half_res.y,
461 );
462 Aabr { min, max }
463 };
464
465 // let window_dim = ui.window_dim();
466 let theme = &ui.theme;
467 let widget_graph = ui.widget_graph();
468 let fonts = &ui.fonts;
469 let dpi_factor = p_scale_factor as f32;
470
471 // We can use information about whether a widget was actually updated to more
472 // easily track cache invalidations.
473 let updated_widgets = ui.updated_widgets();
474 let mut glyph_missing = false;
475
476 updated_widgets.iter()
477 // Filter out widgets that are either:
478 // - not text primitives, or
479 // - are text primitives, but were both already in the cache, and not updated this
480 // frame.
481 //
482 // The reason the second condition is so complicated is that we want to handle cases
483 // where we cleared the whole cache, which can result in glyphs from text updated in a
484 // previous frame not being present in the text cache.
485 .filter_map(|(&widget_id, updated)| {
486 widget_graph.widget(widget_id)
487 .and_then(|widget| Some((widget.rect, widget.unique_widget_state::<widget::Text>()?)))
488 .and_then(|(rect, text)| {
489 // NOTE: This fallback is weird and probably shouldn't exist.
490 let font_id = text.style.font_id(theme)/*.or_else(|| fonts.ids().next())*/?;
491 let font = fonts.get(font_id)?;
492
493 Some((widget_id, updated, rect, text, font_id, font))
494 })
495 })
496 // Recache the entry.
497 .for_each(|(widget_id, updated, rect, graph::UniqueWidgetState { state, style }, font_id, font)| {
498 let entry = match text_cache.entry(widget_id) {
499 Entry::Occupied(_) if !updated => return,
500 entry => entry,
501 };
502
503 // Retrieve styling.
504 let color = style.color(theme);
505 let font_size = style.font_size(theme);
506 let line_spacing = style.line_spacing(theme);
507 let justify = style.justify(theme);
508 let y_align = conrod_core::position::Align::End;
509
510 let text = &state.string;
511 let line_infos = &state.line_infos;
512
513 // Convert conrod coordinates to pixel coordinates.
514 let trans_x = |x: Scalar| (x + ui_win_w / 2.0) * dpi_factor as Scalar;
515 let trans_y = |y: Scalar| ((-y) + ui_win_h / 2.0) * dpi_factor as Scalar;
516
517 // Produce the text layout iterators.
518 let lines = line_infos.iter().map(|info| &text[info.byte_range()]);
519 let line_rects = text::line::rects(line_infos.iter(), font_size, rect,
520 justify, y_align, line_spacing);
521
522 // Grab the positioned glyphs from the text primitives.
523 let scale = text::f32_pt_to_scale(font_size as f32 * dpi_factor);
524 let positioned_glyphs = lines.zip(line_rects).flat_map(|(line, line_rect)| {
525 let (x, y) = (trans_x(line_rect.left()) as f32, trans_y(line_rect.bottom()) as f32);
526 let point = text::rt::Point { x, y };
527 font.layout(line, scale, point)
528 });
529
530 // Reuse the mesh allocation if possible at this entry if possible; we
531 // then clear it to avoid using stale entries.
532 let mesh = entry.or_insert_with(Mesh::new);
533 mesh.clear();
534
535 let color = srgba_to_linear(color.to_fsa().into());
536
537 positioned_glyphs.for_each(|g| {
538 match glyph_cache.rect_for(font_id.index(), &g) {
539 Ok(Some((uv_rect, screen_rect))) => {
540 let uv = Aabr {
541 min: Vec2::new(uv_rect.min.x, uv_rect.max.y),
542 max: Vec2::new(uv_rect.max.x, uv_rect.min.y),
543 };
544 let rect = Aabr {
545 min: Vec2::new(
546 vx(screen_rect.min.x as f64 / p_scale_factor
547 - ui.win_w / 2.0),
548 vy(ui.win_h / 2.0
549 - screen_rect.max.y as f64 / p_scale_factor),
550 ),
551 max: Vec2::new(
552 vx(screen_rect.max.x as f64 / p_scale_factor
553 - ui.win_w / 2.0),
554 vy(ui.win_h / 2.0
555 - screen_rect.min.y as f64 / p_scale_factor),
556 ),
557 };
558 mesh.push_quad(create_ui_quad(rect, uv, color, UiMode::Text));
559 },
560 // Glyph not found, no-op.
561 Ok(None) => {},
562 // Glyph was found, but was not yet cached; we'll need to add it to the
563 // cache and retry.
564 Err(_) => {
565 // Queue the unknown glyph to be cached.
566 glyph_missing = true;
567 }
568 }
569
570 // NOTE: Important to do this for *all* glyphs to try to make sure that
571 // any that are uncached are part of the graph. Because we always
572 // clear the entire cache whenever a new glyph is encountered, by
573 // adding and checking all glyphs as they come in we guarantee that (1)
574 // any glyphs in the text cache are in the queue, and (2) any glyphs
575 // not in the text cache are either in the glyph cache, or (during our
576 // processing here) we set the retry flag to force a glyph cache
577 // update. Setting the flag causes all glyphs in the current queue to
578 // become part of the glyph cache during the second call to
579 // `maintain_internal`, so as long as the cache refresh succeeded,
580 // during the second call all glyphs will hit this branch as desired.
581 glyph_cache.queue_glyph(font_id.index(), g);
582 });
583 });
584
585 if glyph_missing {
586 if *retry {
587 // If a glyph was missing and this was our second try, we know something was
588 // messed up during the glyph_cache redraw. It is possible that
589 // the queue contained unneeded glyphs, so we don't necessarily
590 // have to give up; a more precise enumeration of the
591 // glyphs required to render this frame might work out. However, this is a
592 // pretty remote edge case, so we opt to not care about this
593 // frame (we skip rendering it, basically), and just clear the
594 // text cache and glyph queue; next frame will then
595 // start out with an empty slate, and therefore will enqueue precisely the
596 // glyphs needed for that frame. If *that* frame fails, we're
597 // in bigger trouble.
598 text_cache.clear();
599 glyph_cache.clear();
600 glyph_cache.clear_queue();
601 self.ui.needs_redraw();
602 warn!("Could not recache queued glyphs, skipping frame.");
603 } else {
604 // NOTE: If this is the first round after encountering a new glyph, we just
605 // refresh the whole glyph cache. Technically this is not necessary since
606 // positioned_glyphs should still be accurate, but it's just the easiest way
607 // to ensure that all glyph positions get updated. It also helps keep the glyph
608 // cache reasonable by making sure any glyphs that subsequently get rendered are
609 // actually in the cache, including glyphs that were mapped to ids but didn't
610 // happen to be rendered on the frame where the cache was
611 // refreshed.
612 text_cache.clear();
613 tracing::debug!("Updating glyphs and clearing text cache.");
614
615 if let Err(err) = glyph_cache.cache_queued(|rect, data| {
616 let offset = [rect.min.x, rect.min.y];
617 let size = [rect.width(), rect.height()];
618
619 let new_data = data
620 .iter()
621 .map(|x| [255, 255, 255, *x])
622 .collect::<Vec<[u8; 4]>>();
623
624 renderer.update_texture(&cache_tex.0, offset, size, &new_data);
625 }) {
626 // FIXME: If we actually hit this error, it's still possible we could salvage
627 // things in various ways (for instance, the current queue might have extra
628 // stuff in it, so we could try calling maintain_internal a
629 // third time with a fully clean queue; or we could try to
630 // increase the glyph texture size, etc. But hopefully
631 // we will not actually encounter this error.
632 warn!("Failed to cache queued glyphs: {:?}", err);
633
634 // Clear queued glyphs, so that (hopefully) next time we won't have the
635 // offending glyph or glyph set. We then exit the loop and don't try to
636 // rerender the frame.
637 glyph_cache.clear_queue();
638 self.ui.needs_redraw();
639 } else {
640 // Successfully cached, so repeat the loop.
641 *retry = true;
642 }
643 }
644
645 return;
646 }
647
648 self.draw_commands.clear();
649 let mesh = &mut self.mesh;
650 mesh.clear();
651
652 enum State {
653 Image(TexId),
654 Plain,
655 }
656
657 let mut current_state = State::Plain;
658 let mut start = 0;
659
660 let window_scissor = self.window_scissor;
661 let mut current_scissor = window_scissor;
662
663 let mut ingame_local_index = 0;
664
665 enum Placement {
666 Interface,
667 // Visibility of in world widget.
668 InWorld(bool),
669 }
670
671 let mut placement = Placement::Interface;
672
673 // Switches to the `Plain` state and completes the previous `Command` if not
674 // already in the `Plain` state.
675 macro_rules! switch_to_plain_state {
676 () => {
677 if let State::Image(id) = current_state {
678 self.draw_commands
679 .push(DrawCommand::image(start..mesh.vertices().len(), id));
680 start = mesh.vertices().len();
681 current_state = State::Plain;
682 }
683 };
684 }
685
686 while let Some(prim) = primitives.next() {
687 let Primitive {
688 kind,
689 scizzor,
690 rect,
691 id: widget_id,
692 } = prim;
693 // Check for a change in the scissor.
694 let new_scissor = {
695 let (l, b, w, h) = scizzor.l_b_w_h();
696 let scale_factor = self.scale.scale_factor_physical();
697 // Calculate minimum x and y coordinates while
698 // flipping y axis (from +up to +down) and
699 // moving origin to top-left corner (from middle).
700 let min_x = ui.win_w / 2.0 + l;
701 let min_y = ui.win_h / 2.0 - b - h;
702 let intersection = Aabr {
703 min: Vec2 {
704 x: (min_x * scale_factor) as u16,
705 y: (min_y * scale_factor) as u16,
706 },
707 max: Vec2 {
708 x: ((min_x + w) * scale_factor) as u16,
709 y: ((min_y + h) * scale_factor) as u16,
710 },
711 }
712 .intersection(window_scissor);
713
714 if intersection.is_valid() && intersection.size().map(|s| s > 0).reduce_and() {
715 intersection
716 } else {
717 // TODO: What should we return here
718 // We used to return a zero sized aabr but it's invalid to
719 // use a zero sized scissor so for now we just don't change
720 // the scissor.
721 current_scissor
722 }
723 };
724 if new_scissor != current_scissor {
725 // Finish the current command.
726 self.draw_commands.push(match current_state {
727 State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
728 State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id),
729 });
730 start = mesh.vertices().len();
731
732 // Update the scissor and produce a command.
733 current_scissor = new_scissor;
734 self.draw_commands.push(DrawCommand::Scissor(new_scissor));
735 }
736
737 match placement {
738 // Still drawing primitives ingame
739 Placement::InWorld(visible) => match kind {
740 PrimitiveKind::Other(container) => {
741 // Marker widget indicates the end of primitives from widget wrapped in
742 // `Ingame<W>`. If end marker reached, go back to drawing the interface.
743 if container.type_id
744 == std::any::TypeId::of::<widgets::ingame::IngameEndMarkerState>()
745 {
746 placement = Placement::Interface;
747 if visible {
748 // Finish current state
749 self.draw_commands.push(match current_state {
750 State::Plain => {
751 DrawCommand::plain(start..mesh.vertices().len())
752 },
753 State::Image(id) => {
754 DrawCommand::image(start..mesh.vertices().len(), id)
755 },
756 });
757 start = mesh.vertices().len();
758 // Push new position command
759 self.draw_commands.push(DrawCommand::WorldPos(None));
760 }
761 }
762 },
763 _ => {
764 // Behind the camera
765 if !visible {
766 continue;
767 }
768 },
769 },
770 Placement::Interface => {},
771 }
772
773 match kind {
774 PrimitiveKind::Image {
775 image_id,
776 color,
777 source_rect,
778 } => {
779 let (graphic_id, rotation) = self
780 .image_map
781 .get(&image_id)
782 .expect("Image does not exist in image map");
783 let gl_aabr = gl_aabr(rect);
784 let (source_aabr, gl_size) = {
785 // Transform the source rectangle into uv coordinate.
786 // TODO: Make sure this is right. Especially the conversions.
787 let ((uv_l, uv_r, uv_b, uv_t), gl_size) =
788 match graphic_cache.get_graphic(*graphic_id) {
789 Some(Graphic::Blank) | None => continue,
790 Some(Graphic::Image(image, ..)) => {
791 source_rect.and_then(|src_rect| {
792 let (image_w, image_h) = image.dimensions();
793 let (source_w, source_h) = src_rect.w_h();
794 let gl_size = gl_aabr.size();
795 if image_w == 0
796 || image_h == 0
797 || source_w < 1.0
798 || source_h < 1.0
799 || gl_size.reduce_partial_min() < f32::EPSILON
800 {
801 None
802 } else {
803 // Multiply drawn image size by ratio of original image
804 // size to
805 // source rectangle size (since as the proportion of the
806 // image gets
807 // smaller, the drawn size should get bigger), up to the
808 // actual
809 // size of the original image.
810 let ratio_x = (image_w as f64 / source_w).min(
811 (image_w as f64 / (gl_size.w * half_res.x) as f64)
812 .max(1.0),
813 );
814 let ratio_y = (image_h as f64 / source_h).min(
815 (image_h as f64 / (gl_size.h * half_res.y) as f64)
816 .max(1.0),
817 );
818 let (l, r, b, t) = src_rect.l_r_b_t();
819 Some((
820 (
821 l / image_w as f64, /* * ratio_x */
822 r / image_w as f64, /* * ratio_x */
823 b / image_h as f64, /* * ratio_y */
824 t / image_h as f64, /* * ratio_y */
825 ),
826 Extent2::new(
827 (gl_size.w as f64 * ratio_x) as f32,
828 (gl_size.h as f64 * ratio_y) as f32,
829 ),
830 ))
831 /* ((l / image_w as f64),
832 (r / image_w as f64),
833 (b / image_h as f64),
834 (t / image_h as f64)) */
835 }
836 })
837 },
838 // No easy way to interpret source_rect for voxels...
839 Some(Graphic::Voxel(..)) => None,
840 }
841 .unwrap_or_else(|| ((0.0, 1.0, 0.0, 1.0), gl_aabr.size()));
842 (
843 Aabr {
844 min: Vec2::new(uv_l, uv_b),
845 max: Vec2::new(uv_r, uv_t),
846 },
847 gl_size,
848 )
849 };
850
851 let resolution = Vec2::new(
852 (gl_size.w * half_res.x).round() as u16,
853 (gl_size.h * half_res.y).round() as u16,
854 );
855
856 // Don't do anything if resolution is zero
857 if resolution.map(|e| e == 0).reduce_or() {
858 continue;
859 // TODO: consider logging unneeded elements
860 }
861
862 let color =
863 srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
864
865 // Cache graphic at particular resolution.
866 let (uv_aabr, scale, tex_id) = match graphic_cache.cache_res(
867 renderer,
868 pool,
869 *graphic_id,
870 resolution,
871 source_aabr,
872 *rotation,
873 ) {
874 // TODO: get dims from graphic_cache (or have it return floats directly)
875 Some(((aabr, scale), tex_id)) => {
876 let cache_dims = graphic_cache
877 .get_tex(tex_id)
878 .0
879 .get_dimensions()
880 .xy()
881 .map(|e| e as f32);
882 let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims;
883 let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims;
884 (Aabr { min, max }, scale, tex_id)
885 },
886 None => continue,
887 };
888
889 match current_state {
890 // Switch to the image state if we are not in it already.
891 State::Plain => {
892 self.draw_commands
893 .push(DrawCommand::plain(start..mesh.vertices().len()));
894 start = mesh.vertices().len();
895 current_state = State::Image(tex_id);
896 },
897 // If the image is cached in a different texture switch to the new one
898 State::Image(id) if id != tex_id => {
899 self.draw_commands
900 .push(DrawCommand::image(start..mesh.vertices().len(), id));
901 start = mesh.vertices().len();
902 current_state = State::Image(tex_id);
903 },
904 State::Image(_) => {},
905 }
906
907 mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, match *rotation {
908 Rotation::None | Rotation::Cw90 | Rotation::Cw180 | Rotation::Cw270 => {
909 UiMode::Image { scale }
910 },
911 Rotation::SourceNorth => UiMode::ImageSourceNorth { scale },
912 Rotation::TargetNorth => UiMode::ImageTargetNorth { scale },
913 Rotation::TargetPlayer => UiMode::ImageTargetPlayer { scale },
914 }));
915 },
916 PrimitiveKind::Text { .. } => {
917 switch_to_plain_state!();
918
919 // Mesh should already be cached.
920 mesh.push_mesh(text_cache.get(&widget_id).unwrap_or(&Mesh::new()));
921 },
922 PrimitiveKind::Rectangle { color } => {
923 let color = srgba_to_linear(color.to_fsa().into());
924 // Don't draw a transparent rectangle.
925 if color[3] == 0.0 {
926 continue;
927 }
928
929 switch_to_plain_state!();
930
931 mesh.push_quad(create_ui_quad(
932 gl_aabr(rect),
933 Aabr {
934 min: Vec2::zero(),
935 max: Vec2::zero(),
936 },
937 color,
938 UiMode::Geometry,
939 ));
940 },
941 PrimitiveKind::TrianglesSingleColor { color, triangles } => {
942 // Don't draw transparent triangle or switch state if there are actually no
943 // triangles.
944 let color = srgba_to_linear(Rgba::from(Into::<[f32; 4]>::into(color)));
945 if triangles.is_empty() || color[3] == 0.0 {
946 continue;
947 }
948
949 switch_to_plain_state!();
950
951 for tri in triangles {
952 let p1 = Vec2::new(vx(tri[0][0]), vy(tri[0][1]));
953 let p2 = Vec2::new(vx(tri[1][0]), vy(tri[1][1]));
954 let p3 = Vec2::new(vx(tri[2][0]), vy(tri[2][1]));
955 // If triangle is clockwise, reverse it.
956 let (v1, v2): (Vec3<f32>, Vec3<f32>) = ((p2 - p1).into(), (p3 - p1).into());
957 let triangle = if v1.cross(v2).z > 0.0 {
958 [p1.into_array(), p2.into_array(), p3.into_array()]
959 } else {
960 [p2.into_array(), p1.into_array(), p3.into_array()]
961 };
962 mesh.push_tri(create_ui_tri(
963 triangle,
964 [[0.0; 2]; 3],
965 color,
966 UiMode::Geometry,
967 ));
968 }
969 },
970 PrimitiveKind::Other(container) => {
971 if let Some(s) = container.state_and_style::<widgets::ingame::State, ()>()
972 && let Some(view_projection_mat) = view_projection_mat
973 {
974 // Retrieve world position
975 let parameters = s.state.parameters;
976
977 // Calculate position on the screen at this 3d point using the camera.
978 let pos_on_screen =
979 (view_projection_mat * Vec4::from_point(parameters.pos)).homogenized();
980 let visible = pos_on_screen.z > 0.0 && pos_on_screen.z < 1.0 && {
981 let x = pos_on_screen.x;
982 let y = pos_on_screen.y;
983 let (w, h) = parameters.dims.into_tuple();
984 let (half_w, half_h) = (w / ui_win_w as f32, h / ui_win_h as f32);
985 (x - half_w < 1.0 && x + half_w > -1.0)
986 && (y - half_h < 1.0 && y + half_h > -1.0)
987 };
988 // Don't process ingame elements outside the frustum
989 placement = if visible {
990 // Finish current state
991 self.draw_commands.push(match current_state {
992 State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
993 State::Image(id) => {
994 DrawCommand::image(start..mesh.vertices().len(), id)
995 },
996 });
997 start = mesh.vertices().len();
998
999 // Push new position command
1000 let world_pos = Vec4::from_point(parameters.pos);
1001 if self.ingame_locals.len() > ingame_local_index {
1002 renderer
1003 .update_consts(&mut self.ingame_locals[ingame_local_index], &[
1004 world_pos.into(),
1005 ])
1006 } else {
1007 self.ingame_locals
1008 .push(renderer.create_ui_bound_locals(&[world_pos.into()]));
1009 }
1010 self.draw_commands
1011 .push(DrawCommand::WorldPos(Some(ingame_local_index)));
1012 ingame_local_index += 1;
1013
1014 Placement::InWorld(true)
1015 } else {
1016 Placement::InWorld(false)
1017 };
1018 }
1019 },
1020 _ => {}, /* TODO: Add this.
1021 *PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind
1022 * multicolor with id {:?}", id);} */
1023 }
1024 }
1025 // Enter the final command.
1026 self.draw_commands.push(match current_state {
1027 State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
1028 State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id),
1029 });
1030
1031 /* // Draw glyph cache (use for debugging).
1032 self.draw_commands
1033 .push(DrawCommand::Scissor(default_scissor(renderer)));
1034 start = mesh.vertices().len();
1035 mesh.push_quad(create_ui_quad(
1036 Aabr {
1037 min: (-1.0, -1.0).into(),
1038 max: (1.0, 1.0).into(),
1039 },
1040 Aabr {
1041 min: (0.0, 1.0).into(),
1042 max: (1.0, 0.0).into(),
1043 },
1044 Rgba::new(1.0, 1.0, 1.0, 0.8),
1045 UiMode::Text,
1046 ));
1047 self.draw_commands
1048 .push(DrawCommand::plain(start..mesh.vertices().len())); */
1049
1050 // Create a larger dynamic model if the mesh is larger than the current model
1051 // size.
1052 if self.model.len() < self.mesh.vertices().len() {
1053 self.model = renderer.create_dynamic_model(self.mesh.vertices().len() * 4 / 3);
1054 }
1055 // Update model with new mesh.
1056 renderer.update_model(&self.model, &self.mesh, 0);
1057 }
1058
1059 pub fn render<'pass, 'data: 'pass>(&'data self, drawer: &mut UiDrawer<'_, 'pass>) {
1060 span!(_guard, "render", "Ui::render");
1061 let mut drawer = drawer.prepare(&self.interface_locals, &self.model, self.window_scissor);
1062 for draw_command in self.draw_commands.iter() {
1063 match draw_command {
1064 DrawCommand::Scissor(new_scissor) => {
1065 drawer.set_scissor(*new_scissor);
1066 },
1067 DrawCommand::WorldPos(index) => {
1068 drawer.set_locals(
1069 index.map_or(&self.interface_locals, |i| &self.ingame_locals[i]),
1070 );
1071 },
1072 DrawCommand::Draw { kind, verts } => {
1073 let tex = match kind {
1074 DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id),
1075 DrawKind::Plain => self.cache.glyph_cache_tex(),
1076 };
1077 drawer.draw(tex.1, verts.clone()); // Note: trivial clone
1078 },
1079 }
1080 }
1081 }
1082}
1083
1084fn default_scissor(physical_resolution: Vec2<u32>) -> Aabr<u16> {
1085 let (screen_w, screen_h) = physical_resolution.into_tuple();
1086 Aabr {
1087 min: Vec2 { x: 0, y: 0 },
1088 max: Vec2 {
1089 x: screen_w as u16,
1090 y: screen_h as u16,
1091 },
1092 }
1093}