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