Skip to main content

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}