2019-04-15 06:07:56 +00:00
|
|
|
mod graphic;
|
2019-04-19 18:55:38 +00:00
|
|
|
mod util;
|
2019-04-29 20:37:19 +00:00
|
|
|
mod widgets;
|
2019-04-26 03:30:46 +00:00
|
|
|
#[macro_use]
|
|
|
|
mod img_ids;
|
2019-03-16 02:03:21 +00:00
|
|
|
|
2019-04-15 06:07:56 +00:00
|
|
|
pub use graphic::Graphic;
|
2019-04-29 20:37:19 +00:00
|
|
|
pub(self) use util::{linear_to_srgb, srgb_to_linear};
|
|
|
|
pub use widgets::toggle_button::ToggleButton;
|
2019-01-30 12:11:34 +00:00
|
|
|
|
|
|
|
use crate::{
|
|
|
|
render::{
|
2019-04-29 20:37:19 +00:00
|
|
|
create_ui_quad, create_ui_tri, Mesh, Model, RenderError, Renderer, Texture, UiMode,
|
2019-01-30 12:11:34 +00:00
|
|
|
UiPipeline,
|
|
|
|
},
|
2019-02-16 03:01:42 +00:00
|
|
|
window::Window,
|
2019-04-29 20:37:19 +00:00
|
|
|
Error,
|
|
|
|
};
|
|
|
|
use conrod_core::{
|
|
|
|
event::Input,
|
|
|
|
image::{Id as ImgId, Map},
|
|
|
|
input::{touch::Touch, Button, Motion, Widget},
|
|
|
|
render::Primitive,
|
|
|
|
text::{font::Id as FontId, Font, GlyphCache},
|
|
|
|
widget::{id::Generator, Id as WidgId},
|
|
|
|
Ui as CrUi, UiBuilder, UiCell,
|
2019-01-30 12:11:34 +00:00
|
|
|
};
|
2019-04-29 20:37:19 +00:00
|
|
|
use graphic::{GraphicCache, Id as GraphicId};
|
|
|
|
use vek::*;
|
2019-01-30 12:11:34 +00:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum UiError {
|
|
|
|
RenderError(RenderError),
|
|
|
|
}
|
2019-03-22 03:55:42 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Event(Input);
|
|
|
|
impl Event {
|
|
|
|
pub fn try_from(event: glutin::Event, window: &glutin::GlWindow) -> Option<Self> {
|
2019-04-13 14:33:42 +00:00
|
|
|
use conrod_winit::*;
|
|
|
|
use winit;
|
|
|
|
// A wrapper around the winit window that allows us to implement the trait necessary for enabling
|
|
|
|
// the winit <-> conrod conversion functions.
|
|
|
|
struct WindowRef<'a>(&'a winit::Window);
|
|
|
|
|
|
|
|
// Implement the `WinitWindow` trait for `WindowRef` to allow for generating compatible conversion
|
|
|
|
// functions.
|
|
|
|
impl<'a> conrod_winit::WinitWindow for WindowRef<'a> {
|
|
|
|
fn get_inner_size(&self) -> Option<(u32, u32)> {
|
|
|
|
winit::Window::get_inner_size(&self.0).map(Into::into)
|
|
|
|
}
|
|
|
|
fn hidpi_factor(&self) -> f32 {
|
|
|
|
winit::Window::get_hidpi_factor(&self.0) as _
|
|
|
|
}
|
|
|
|
}
|
2019-04-29 20:37:19 +00:00
|
|
|
convert_event!(event, &WindowRef(window.window())).map(|input| Self(input))
|
2019-03-22 03:55:42 +00:00
|
|
|
}
|
|
|
|
pub fn is_keyboard_or_mouse(&self) -> bool {
|
|
|
|
match self.0 {
|
2019-04-29 20:37:19 +00:00
|
|
|
Input::Press(_)
|
|
|
|
| Input::Release(_)
|
|
|
|
| Input::Motion(_)
|
|
|
|
| Input::Touch(_)
|
|
|
|
| Input::Text(_) => true,
|
2019-03-22 03:55:42 +00:00
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
2019-03-30 02:15:27 +00:00
|
|
|
pub fn is_keyboard(&self) -> bool {
|
|
|
|
match self.0 {
|
2019-04-29 20:37:19 +00:00
|
|
|
Input::Press(Button::Keyboard(_))
|
|
|
|
| Input::Release(Button::Keyboard(_))
|
|
|
|
| Input::Text(_) => true,
|
2019-03-30 02:15:27 +00:00
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
2019-03-28 04:42:04 +00:00
|
|
|
pub fn new_resize(dims: Vec2<f64>) -> Self {
|
|
|
|
Self(Input::Resize(dims.x, dims.y))
|
|
|
|
}
|
2019-03-22 03:55:42 +00:00
|
|
|
}
|
2019-01-30 12:11:34 +00:00
|
|
|
|
|
|
|
pub struct Cache {
|
2019-02-23 02:41:52 +00:00
|
|
|
glyph_cache: GlyphCache<'static>,
|
|
|
|
glyph_cache_tex: Texture<UiPipeline>,
|
2019-04-15 06:07:56 +00:00
|
|
|
graphic_cache: graphic::GraphicCache,
|
|
|
|
graphic_cache_tex: Texture<UiPipeline>,
|
2019-01-30 12:11:34 +00:00
|
|
|
}
|
|
|
|
|
2019-02-16 03:01:42 +00:00
|
|
|
// TODO: Should functions be returning UiError instead of Error?
|
2019-01-30 12:11:34 +00:00
|
|
|
impl Cache {
|
|
|
|
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
|
2019-02-23 03:16:12 +00:00
|
|
|
let (w, h) = renderer.get_resolution().into_tuple();
|
2019-02-23 02:41:52 +00:00
|
|
|
const SCALE_TOLERANCE: f32 = 0.1;
|
|
|
|
const POSITION_TOLERANCE: f32 = 0.1;
|
|
|
|
|
2019-04-15 06:07:56 +00:00
|
|
|
let graphic_cache_dims = Vec2::new(w * 4, h * 4);
|
2019-01-30 12:11:34 +00:00
|
|
|
Ok(Self {
|
2019-02-23 02:41:52 +00:00
|
|
|
glyph_cache: GlyphCache::builder()
|
|
|
|
.dimensions(w as u32, h as u32)
|
|
|
|
.scale_tolerance(SCALE_TOLERANCE)
|
|
|
|
.position_tolerance(POSITION_TOLERANCE)
|
|
|
|
.build(),
|
|
|
|
glyph_cache_tex: renderer.create_dynamic_texture((w, h).into())?,
|
2019-04-15 06:07:56 +00:00
|
|
|
graphic_cache: GraphicCache::new(graphic_cache_dims),
|
|
|
|
graphic_cache_tex: renderer.create_dynamic_texture(graphic_cache_dims)?,
|
2019-01-30 12:11:34 +00:00
|
|
|
})
|
|
|
|
}
|
2019-04-29 20:37:19 +00:00
|
|
|
pub fn glyph_cache_tex(&self) -> &Texture<UiPipeline> {
|
|
|
|
&self.glyph_cache_tex
|
|
|
|
}
|
|
|
|
pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture<UiPipeline>) {
|
|
|
|
(&mut self.glyph_cache, &self.glyph_cache_tex)
|
|
|
|
}
|
|
|
|
pub fn graphic_cache_tex(&self) -> &Texture<UiPipeline> {
|
|
|
|
&self.graphic_cache_tex
|
|
|
|
}
|
|
|
|
pub fn graphic_cache_mut_and_tex(&mut self) -> (&mut GraphicCache, &Texture<UiPipeline>) {
|
|
|
|
(&mut self.graphic_cache, &self.graphic_cache_tex)
|
|
|
|
}
|
|
|
|
pub fn new_graphic(&mut self, graphic: Graphic) -> GraphicId {
|
|
|
|
self.graphic_cache.new_graphic(graphic)
|
|
|
|
}
|
2019-04-20 01:35:12 +00:00
|
|
|
pub fn clear_graphic_cache(&mut self, renderer: &mut Renderer, new_size: Vec2<u16>) {
|
|
|
|
self.graphic_cache.clear_cache(new_size);
|
|
|
|
self.graphic_cache_tex = renderer.create_dynamic_texture(new_size).unwrap();
|
|
|
|
}
|
2019-01-30 12:11:34 +00:00
|
|
|
}
|
|
|
|
|
2019-03-20 05:13:42 +00:00
|
|
|
enum DrawKind {
|
2019-04-15 06:07:56 +00:00
|
|
|
Image,
|
2019-02-23 02:41:52 +00:00
|
|
|
// Text and non-textured geometry
|
2019-03-20 05:13:42 +00:00
|
|
|
Plain,
|
|
|
|
}
|
|
|
|
enum DrawCommand {
|
|
|
|
Draw {
|
|
|
|
kind: DrawKind,
|
|
|
|
model: Model<UiPipeline>,
|
|
|
|
},
|
|
|
|
Scissor(Aabr<u16>),
|
|
|
|
}
|
|
|
|
impl DrawCommand {
|
2019-04-15 06:07:56 +00:00
|
|
|
fn image(model: Model<UiPipeline>) -> DrawCommand {
|
2019-03-20 05:13:42 +00:00
|
|
|
DrawCommand::Draw {
|
2019-04-15 06:07:56 +00:00
|
|
|
kind: DrawKind::Image,
|
2019-03-20 05:13:42 +00:00
|
|
|
model,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn plain(model: Model<UiPipeline>) -> DrawCommand {
|
|
|
|
DrawCommand::Draw {
|
|
|
|
kind: DrawKind::Plain,
|
|
|
|
model,
|
|
|
|
}
|
|
|
|
}
|
2019-02-16 03:01:42 +00:00
|
|
|
}
|
|
|
|
|
2019-03-03 23:55:07 +00:00
|
|
|
// How to scale the ui
|
|
|
|
pub enum ScaleMode {
|
|
|
|
// Scale against physical size
|
|
|
|
Absolute(f64),
|
|
|
|
// Use the dpi factor provided by the windowing system (i.e. use logical size)
|
|
|
|
DpiFactor,
|
|
|
|
// Scale based on the window's physical size, but maintain aspect ratio of widgets
|
|
|
|
// Contains width and height of the "default" window size (ie where there should be no scaling)
|
|
|
|
RelativeToWindow(Vec2<f64>),
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Scale {
|
|
|
|
// Type of scaling to use
|
|
|
|
mode: ScaleMode,
|
|
|
|
// Current dpi factor
|
|
|
|
dpi_factor: f64,
|
|
|
|
// Current logical window size
|
|
|
|
window_dims: Vec2<f64>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Scale {
|
|
|
|
fn new(window: &Window, mode: ScaleMode) -> Self {
|
|
|
|
let window_dims = window.logical_size();
|
|
|
|
let dpi_factor = window.renderer().get_resolution().x as f64 / window_dims.x;
|
|
|
|
Scale {
|
|
|
|
mode,
|
|
|
|
dpi_factor,
|
|
|
|
window_dims,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Change the scaling mode
|
|
|
|
pub fn scaling_mode(&mut self, mode: ScaleMode) {
|
|
|
|
self.mode = mode;
|
|
|
|
}
|
2019-03-20 05:13:42 +00:00
|
|
|
// Calculate factor to transform between logical coordinates and our scaled coordinates
|
|
|
|
fn scale_factor_logical(&self) -> f64 {
|
2019-03-03 23:55:07 +00:00
|
|
|
match self.mode {
|
|
|
|
ScaleMode::Absolute(scale) => scale / self.dpi_factor,
|
|
|
|
ScaleMode::DpiFactor => 1.0,
|
2019-04-29 20:37:19 +00:00
|
|
|
ScaleMode::RelativeToWindow(dims) => {
|
|
|
|
(self.window_dims.x / dims.x).min(self.window_dims.y / dims.y)
|
|
|
|
}
|
2019-03-03 23:55:07 +00:00
|
|
|
}
|
|
|
|
}
|
2019-03-20 05:13:42 +00:00
|
|
|
// Calculate factor to transform between physical coordinates and our scaled coordinates
|
|
|
|
fn scale_factor_physical(&self) -> f64 {
|
|
|
|
self.scale_factor_logical() * self.dpi_factor
|
|
|
|
}
|
2019-03-03 23:55:07 +00:00
|
|
|
// Updates internal window size (and/or dpi_factor)
|
|
|
|
fn window_resized(&mut self, new_dims: Vec2<f64>, renderer: &Renderer) {
|
|
|
|
self.dpi_factor = renderer.get_resolution().x as f64 / new_dims.x;
|
|
|
|
self.window_dims = new_dims;
|
|
|
|
}
|
|
|
|
// Get scaled window size
|
|
|
|
fn scaled_window_size(&self) -> Vec2<f64> {
|
2019-03-20 05:13:42 +00:00
|
|
|
self.window_dims / self.scale_factor_logical()
|
2019-03-03 23:55:07 +00:00
|
|
|
}
|
|
|
|
// Transform point from logical to scaled coordinates
|
|
|
|
fn scale_point(&self, point: Vec2<f64>) -> Vec2<f64> {
|
2019-03-20 05:13:42 +00:00
|
|
|
point / self.scale_factor_logical()
|
2019-03-03 23:55:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-30 12:11:34 +00:00
|
|
|
pub struct Ui {
|
2019-02-12 04:14:55 +00:00
|
|
|
ui: CrUi,
|
2019-04-15 06:07:56 +00:00
|
|
|
image_map: Map<GraphicId>,
|
2019-01-30 12:11:34 +00:00
|
|
|
cache: Cache,
|
2019-02-23 03:16:12 +00:00
|
|
|
// Draw commands for the next render
|
2019-02-23 02:41:52 +00:00
|
|
|
draw_commands: Vec<DrawCommand>,
|
2019-03-03 23:55:07 +00:00
|
|
|
// Stores new window size for updating scaling
|
|
|
|
window_resized: Option<Vec2<f64>>,
|
|
|
|
// Scaling of the ui
|
|
|
|
scale: Scale,
|
2019-01-30 12:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Ui {
|
2019-02-16 03:01:42 +00:00
|
|
|
pub fn new(window: &mut Window) -> Result<Self, Error> {
|
2019-03-03 23:55:07 +00:00
|
|
|
let scale = Scale::new(window, ScaleMode::Absolute(1.0));
|
|
|
|
let win_dims = scale.scaled_window_size().into_array();
|
2019-04-25 12:20:35 +00:00
|
|
|
|
2019-01-30 12:11:34 +00:00
|
|
|
Ok(Self {
|
2019-03-03 23:55:07 +00:00
|
|
|
ui: UiBuilder::new(win_dims).build(),
|
2019-02-12 04:14:55 +00:00
|
|
|
image_map: Map::new(),
|
2019-02-16 03:01:42 +00:00
|
|
|
cache: Cache::new(window.renderer_mut())?,
|
2019-03-03 23:55:07 +00:00
|
|
|
window_resized: None,
|
2019-02-23 02:41:52 +00:00
|
|
|
draw_commands: vec![],
|
2019-03-03 23:55:07 +00:00
|
|
|
scale,
|
2019-01-30 12:11:34 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-03-03 23:55:07 +00:00
|
|
|
// Set the scaling mode of the ui
|
|
|
|
pub fn scaling_mode(&mut self, mode: ScaleMode) {
|
|
|
|
self.scale.scaling_mode(mode);
|
|
|
|
// Give conrod the new size
|
|
|
|
let (w, h) = self.scale.scaled_window_size().into_tuple();
|
|
|
|
self.ui.handle_event(Input::Resize(w, h));
|
|
|
|
}
|
|
|
|
|
2019-04-15 06:07:56 +00:00
|
|
|
pub fn new_graphic(&mut self, graphic: Graphic) -> ImgId {
|
|
|
|
self.image_map.insert(self.cache.new_graphic(graphic))
|
2019-02-12 04:14:55 +00:00
|
|
|
}
|
|
|
|
|
2019-02-23 02:41:52 +00:00
|
|
|
pub fn new_font(&mut self, font: Font) -> FontId {
|
|
|
|
self.ui.fonts.insert(font)
|
|
|
|
}
|
|
|
|
|
2019-02-16 03:01:42 +00:00
|
|
|
pub fn id_generator(&mut self) -> Generator {
|
|
|
|
self.ui.widget_id_generator()
|
2019-02-12 04:14:55 +00:00
|
|
|
}
|
|
|
|
|
2019-02-16 03:01:42 +00:00
|
|
|
pub fn set_widgets(&mut self) -> UiCell {
|
|
|
|
self.ui.set_widgets()
|
2019-02-12 04:14:55 +00:00
|
|
|
}
|
|
|
|
|
2019-04-15 06:07:56 +00:00
|
|
|
// Accepts Option so widget can be unfocused
|
2019-03-30 02:15:27 +00:00
|
|
|
pub fn focus_widget(&mut self, id: Option<WidgId>) {
|
2019-04-15 00:45:54 +00:00
|
|
|
self.ui.keyboard_capture(match id {
|
|
|
|
Some(id) => id,
|
|
|
|
None => self.ui.window,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get id of current widget capturing keyboard
|
|
|
|
pub fn widget_capturing_keyboard(&self) -> Option<WidgId> {
|
|
|
|
self.ui.global_input().current.widget_capturing_keyboard
|
2019-03-30 02:15:27 +00:00
|
|
|
}
|
|
|
|
|
2019-04-15 06:07:56 +00:00
|
|
|
// Get whether a widget besides the window is capturing the mouse
|
2019-04-15 00:15:12 +00:00
|
|
|
pub fn no_widget_capturing_mouse(&self) -> bool {
|
2019-04-29 20:37:19 +00:00
|
|
|
self.ui
|
|
|
|
.global_input()
|
|
|
|
.current
|
|
|
|
.widget_capturing_mouse
|
|
|
|
.filter(|id| id != &self.ui.window)
|
|
|
|
.is_none()
|
2019-04-15 00:15:12 +00:00
|
|
|
}
|
2019-03-30 02:15:27 +00:00
|
|
|
|
2019-03-22 03:55:42 +00:00
|
|
|
pub fn handle_event(&mut self, event: Event) {
|
|
|
|
match event.0 {
|
2019-03-03 23:55:07 +00:00
|
|
|
Input::Resize(w, h) => self.window_resized = Some(Vec2::new(w, h)),
|
2019-04-29 20:37:19 +00:00
|
|
|
Input::Touch(touch) => self.ui.handle_event(Input::Touch(Touch {
|
|
|
|
xy: self.scale.scale_point(touch.xy.into()).into_array(),
|
|
|
|
..touch
|
|
|
|
})),
|
|
|
|
Input::Motion(motion) => self.ui.handle_event(Input::Motion(match motion {
|
|
|
|
Motion::MouseCursor { x, y } => {
|
|
|
|
let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
|
|
|
|
Motion::MouseCursor { x, y }
|
|
|
|
}
|
|
|
|
Motion::MouseRelative { x, y } => {
|
|
|
|
let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
|
|
|
|
Motion::MouseRelative { x, y }
|
|
|
|
}
|
|
|
|
Motion::Scroll { x, y } => {
|
|
|
|
let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
|
|
|
|
Motion::Scroll { x, y }
|
|
|
|
}
|
|
|
|
_ => motion,
|
|
|
|
})),
|
2019-03-22 03:55:42 +00:00
|
|
|
_ => self.ui.handle_event(event.0),
|
2019-03-03 23:55:07 +00:00
|
|
|
}
|
2019-02-16 03:01:42 +00:00
|
|
|
}
|
2019-02-12 04:14:55 +00:00
|
|
|
|
2019-02-16 03:01:42 +00:00
|
|
|
pub fn widget_input(&self, id: WidgId) -> Widget {
|
|
|
|
self.ui.widget_input(id)
|
2019-01-30 12:11:34 +00:00
|
|
|
}
|
|
|
|
|
2019-02-16 03:01:42 +00:00
|
|
|
pub fn maintain(&mut self, renderer: &mut Renderer) {
|
|
|
|
let ref mut ui = self.ui;
|
2019-03-03 23:55:07 +00:00
|
|
|
// Regenerate draw commands and associated models only if the ui changed
|
2019-02-16 03:01:42 +00:00
|
|
|
if let Some(mut primitives) = ui.draw_if_changed() {
|
2019-02-23 02:41:52 +00:00
|
|
|
self.draw_commands.clear();
|
|
|
|
let mut mesh = Mesh::new();
|
|
|
|
|
2019-04-15 06:07:56 +00:00
|
|
|
// TODO: this could be removed entirely if the draw call just used both textures
|
2019-04-19 18:14:00 +00:00
|
|
|
// however this allows for flexibility if we want to interleave other draw calls later
|
2019-04-15 06:07:56 +00:00
|
|
|
enum State {
|
|
|
|
Image,
|
|
|
|
Plain,
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut current_state = State::Plain;
|
2019-02-23 02:41:52 +00:00
|
|
|
|
2019-03-20 05:13:42 +00:00
|
|
|
let window_scizzor = default_scissor(renderer);
|
|
|
|
let mut current_scizzor = window_scizzor;
|
|
|
|
|
2019-02-23 02:41:52 +00:00
|
|
|
// Switches to the `Plain` state and completes the previous `Command` if not already in the
|
|
|
|
// `Plain` state.
|
|
|
|
macro_rules! switch_to_plain_state {
|
|
|
|
() => {
|
2019-04-15 06:07:56 +00:00
|
|
|
if let State::Image = current_state {
|
2019-04-29 20:37:19 +00:00
|
|
|
self.draw_commands
|
|
|
|
.push(DrawCommand::image(renderer.create_model(&mesh).unwrap()));
|
2019-02-23 02:41:52 +00:00
|
|
|
mesh.clear();
|
2019-04-15 06:07:56 +00:00
|
|
|
current_state = State::Plain;
|
2019-02-23 02:41:52 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-03-20 05:13:42 +00:00
|
|
|
let p_scale_factor = self.scale.scale_factor_physical();
|
|
|
|
|
2019-02-12 04:14:55 +00:00
|
|
|
while let Some(prim) = primitives.next() {
|
2019-04-29 20:37:19 +00:00
|
|
|
let Primitive {
|
|
|
|
kind,
|
|
|
|
scizzor,
|
|
|
|
id: _id,
|
|
|
|
rect,
|
|
|
|
} = prim;
|
2019-03-20 05:13:42 +00:00
|
|
|
|
|
|
|
// Check for a change in the scizzor
|
|
|
|
let new_scizzor = {
|
|
|
|
let (l, b, w, h) = scizzor.l_b_w_h();
|
|
|
|
// Calculate minimum x and y coordinates while
|
|
|
|
// - flipping y axis (from +up to +down)
|
|
|
|
// - moving origin to top-left corner (from middle)
|
|
|
|
let min_x = ui.win_w / 2.0 + l;
|
|
|
|
let min_y = ui.win_h / 2.0 - b - h;
|
|
|
|
Aabr {
|
|
|
|
min: Vec2 {
|
|
|
|
x: (min_x * p_scale_factor) as u16,
|
|
|
|
y: (min_y * p_scale_factor) as u16,
|
|
|
|
},
|
|
|
|
max: Vec2 {
|
|
|
|
x: ((min_x + w) * p_scale_factor) as u16,
|
|
|
|
y: ((min_y + h) * p_scale_factor) as u16,
|
2019-04-29 20:37:19 +00:00
|
|
|
},
|
2019-03-20 05:13:42 +00:00
|
|
|
}
|
|
|
|
.intersection(window_scizzor)
|
|
|
|
};
|
|
|
|
if new_scizzor != current_scizzor {
|
|
|
|
// Finish the current command
|
2019-04-15 06:07:56 +00:00
|
|
|
self.draw_commands.push(match current_state {
|
2019-04-29 20:37:19 +00:00
|
|
|
State::Plain => DrawCommand::plain(renderer.create_model(&mesh).unwrap()),
|
|
|
|
State::Image => DrawCommand::image(renderer.create_model(&mesh).unwrap()),
|
2019-04-15 06:07:56 +00:00
|
|
|
});
|
2019-03-20 05:13:42 +00:00
|
|
|
mesh.clear();
|
|
|
|
|
|
|
|
// Update the scizzor and produce a command.
|
|
|
|
current_scizzor = new_scizzor;
|
|
|
|
self.draw_commands.push(DrawCommand::Scissor(new_scizzor));
|
|
|
|
}
|
|
|
|
|
2019-03-04 07:28:16 +00:00
|
|
|
// Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0)
|
|
|
|
let vx = |x: f64| (x / ui.win_w * 2.0) as f32;
|
|
|
|
let vy = |y: f64| (y / ui.win_h * 2.0) as f32;
|
2019-03-20 05:13:42 +00:00
|
|
|
let gl_aabr = |rect: conrod_core::Rect| {
|
|
|
|
let (l, r, b, t) = rect.l_r_b_t();
|
|
|
|
Aabr {
|
|
|
|
min: Vec2::new(vx(l), vy(b)),
|
|
|
|
max: Vec2::new(vx(r), vy(t)),
|
|
|
|
}
|
|
|
|
};
|
2019-02-23 03:16:12 +00:00
|
|
|
|
2019-02-23 02:41:52 +00:00
|
|
|
use conrod_core::render::PrimitiveKind;
|
2019-02-12 04:14:55 +00:00
|
|
|
match kind {
|
2019-04-29 20:37:19 +00:00
|
|
|
PrimitiveKind::Image {
|
|
|
|
image_id,
|
|
|
|
color,
|
|
|
|
source_rect,
|
|
|
|
} => {
|
|
|
|
let graphic_id = self
|
|
|
|
.image_map
|
|
|
|
.get(&image_id)
|
|
|
|
.expect("Image does not exist in image map");
|
2019-04-19 18:14:00 +00:00
|
|
|
let (graphic_cache, cache_tex) = self.cache.graphic_cache_mut_and_tex();
|
|
|
|
|
|
|
|
match graphic_cache.get_graphic(*graphic_id) {
|
|
|
|
Some(Graphic::Blank) | None => continue,
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
2019-02-23 02:41:52 +00:00
|
|
|
// Switch to the `Image` state for this image if we're not in it already.
|
2019-04-15 06:07:56 +00:00
|
|
|
if let State::Plain = current_state {
|
2019-04-29 20:37:19 +00:00
|
|
|
self.draw_commands
|
|
|
|
.push(DrawCommand::plain(renderer.create_model(&mesh).unwrap()));
|
2019-04-15 06:07:56 +00:00
|
|
|
mesh.clear();
|
|
|
|
current_state = State::Image;
|
2019-02-23 02:41:52 +00:00
|
|
|
}
|
|
|
|
|
2019-04-29 20:37:19 +00:00
|
|
|
let color = srgb_to_linear(
|
|
|
|
color.unwrap_or(conrod_core::color::WHITE).to_fsa().into(),
|
|
|
|
);
|
2019-04-15 06:07:56 +00:00
|
|
|
|
|
|
|
let resolution = Vec2::new(
|
|
|
|
(rect.w() * p_scale_factor) as u16,
|
|
|
|
(rect.h() * p_scale_factor) as u16,
|
|
|
|
);
|
|
|
|
// Transform the source rectangle into uv coordinate
|
|
|
|
// TODO: make sure this is right
|
|
|
|
let source_aabr = {
|
2019-04-29 20:37:19 +00:00
|
|
|
let (uv_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0); /*match source_rect {
|
|
|
|
Some(src_rect) => {
|
|
|
|
let (l, r, b, t) = src_rect.l_r_b_t();
|
|
|
|
((l / image_w) as f32,
|
|
|
|
(r / image_w) as f32,
|
|
|
|
(b / image_h) as f32,
|
|
|
|
(t / image_h) as f32)
|
|
|
|
}
|
|
|
|
None => (0.0, 1.0, 0.0, 1.0),
|
|
|
|
};*/
|
2019-04-15 06:07:56 +00:00
|
|
|
Aabr {
|
|
|
|
min: Vec2::new(uv_l, uv_b),
|
|
|
|
max: Vec2::new(uv_r, uv_t),
|
2019-02-23 02:41:52 +00:00
|
|
|
}
|
2019-02-28 00:37:47 +00:00
|
|
|
};
|
2019-04-29 20:37:19 +00:00
|
|
|
let (cache_w, cache_h) =
|
|
|
|
cache_tex.get_dimensions().map(|e| e as f32).into_tuple();
|
2019-04-15 06:07:56 +00:00
|
|
|
|
|
|
|
// Cache graphic at particular resolution
|
2019-04-29 20:37:19 +00:00
|
|
|
let uv_aabr = match graphic_cache.cache_res(
|
|
|
|
*graphic_id,
|
|
|
|
resolution,
|
|
|
|
source_aabr,
|
|
|
|
|aabr, data| {
|
|
|
|
let offset = aabr.min.into_array();
|
|
|
|
let size = aabr.size().into_array();
|
|
|
|
renderer.update_texture(cache_tex, offset, size, &data);
|
|
|
|
},
|
|
|
|
) {
|
2019-04-15 06:07:56 +00:00
|
|
|
Some(aabr) => Aabr {
|
2019-04-29 20:37:19 +00:00
|
|
|
min: Vec2::new(
|
|
|
|
aabr.min.x as f32 / cache_w,
|
|
|
|
aabr.max.y as f32 / cache_h,
|
|
|
|
),
|
|
|
|
max: Vec2::new(
|
|
|
|
aabr.max.x as f32 / cache_w,
|
|
|
|
aabr.min.y as f32 / cache_h,
|
|
|
|
),
|
2019-04-15 06:07:56 +00:00
|
|
|
},
|
|
|
|
None => continue,
|
2019-03-20 05:13:42 +00:00
|
|
|
};
|
2019-04-15 06:07:56 +00:00
|
|
|
|
2019-04-04 14:45:57 +00:00
|
|
|
mesh.push_quad(create_ui_quad(
|
2019-03-20 05:13:42 +00:00
|
|
|
gl_aabr(rect),
|
2019-04-15 06:07:56 +00:00
|
|
|
uv_aabr,
|
2019-02-23 02:41:52 +00:00
|
|
|
color,
|
|
|
|
UiMode::Image,
|
2019-04-04 14:45:57 +00:00
|
|
|
));
|
2019-02-23 02:41:52 +00:00
|
|
|
}
|
2019-04-29 20:37:19 +00:00
|
|
|
PrimitiveKind::Text {
|
|
|
|
color,
|
|
|
|
text,
|
|
|
|
font_id,
|
|
|
|
} => {
|
2019-02-23 02:41:52 +00:00
|
|
|
switch_to_plain_state!();
|
2019-03-20 05:13:42 +00:00
|
|
|
// Get screen width and height
|
2019-04-29 20:37:19 +00:00
|
|
|
let (screen_w, screen_h) =
|
|
|
|
renderer.get_resolution().map(|e| e as f32).into_tuple();
|
2019-02-23 02:41:52 +00:00
|
|
|
// Calculate dpi factor
|
|
|
|
let dpi_factor = screen_w / ui.win_w as f32;
|
|
|
|
|
|
|
|
let positioned_glyphs = text.positioned_glyphs(dpi_factor);
|
|
|
|
let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex();
|
|
|
|
// Queue the glyphs to be cached
|
|
|
|
for glyph in positioned_glyphs {
|
|
|
|
glyph_cache.queue_glyph(font_id.index(), glyph.clone());
|
|
|
|
}
|
|
|
|
|
2019-04-29 20:37:19 +00:00
|
|
|
glyph_cache
|
|
|
|
.cache_queued(|rect, data| {
|
|
|
|
let offset = [rect.min.x as u16, rect.min.y as u16];
|
|
|
|
let size = [rect.width() as u16, rect.height() as u16];
|
2019-02-23 02:41:52 +00:00
|
|
|
|
2019-04-29 20:37:19 +00:00
|
|
|
let new_data = data
|
|
|
|
.iter()
|
|
|
|
.map(|x| [255, 255, 255, *x])
|
|
|
|
.collect::<Vec<[u8; 4]>>();
|
2019-02-23 02:41:52 +00:00
|
|
|
|
2019-04-29 20:37:19 +00:00
|
|
|
renderer.update_texture(cache_tex, offset, size, &new_data);
|
|
|
|
})
|
|
|
|
.unwrap();
|
2019-02-23 02:41:52 +00:00
|
|
|
|
2019-04-19 18:55:38 +00:00
|
|
|
let color = srgb_to_linear(color.to_fsa().into());
|
2019-02-23 02:41:52 +00:00
|
|
|
|
|
|
|
for g in positioned_glyphs {
|
2019-04-29 20:37:19 +00:00
|
|
|
if let Ok(Some((uv_rect, screen_rect))) =
|
|
|
|
glyph_cache.rect_for(font_id.index(), g)
|
|
|
|
{
|
2019-03-20 05:13:42 +00:00
|
|
|
let uv = Aabr {
|
|
|
|
min: Vec2::new(uv_rect.min.x, uv_rect.max.y),
|
|
|
|
max: Vec2::new(uv_rect.max.x, uv_rect.min.y),
|
|
|
|
};
|
|
|
|
let rect = Aabr {
|
|
|
|
min: Vec2::new(
|
2019-04-29 20:37:19 +00:00
|
|
|
(screen_rect.min.x as f32 / screen_w - 0.5) * 2.0,
|
2019-03-20 05:13:42 +00:00
|
|
|
(screen_rect.max.y as f32 / screen_h - 0.5) * -2.0,
|
|
|
|
),
|
|
|
|
max: Vec2::new(
|
2019-04-29 20:37:19 +00:00
|
|
|
(screen_rect.max.x as f32 / screen_w - 0.5) * 2.0,
|
2019-03-20 05:13:42 +00:00
|
|
|
(screen_rect.min.y as f32 / screen_h - 0.5) * -2.0,
|
|
|
|
),
|
|
|
|
};
|
2019-04-29 20:37:19 +00:00
|
|
|
mesh.push_quad(create_ui_quad(rect, uv, color, UiMode::Text));
|
2019-02-23 02:41:52 +00:00
|
|
|
}
|
|
|
|
}
|
2019-02-12 04:14:55 +00:00
|
|
|
}
|
2019-03-04 07:28:16 +00:00
|
|
|
PrimitiveKind::Rectangle { color } => {
|
2019-04-19 18:55:38 +00:00
|
|
|
let color = srgb_to_linear(color.to_fsa().into());
|
2019-03-25 02:15:29 +00:00
|
|
|
// Don't draw a transparent rectangle
|
2019-03-04 07:28:16 +00:00
|
|
|
if color[3] == 0.0 {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch_to_plain_state!();
|
|
|
|
|
2019-04-04 14:45:57 +00:00
|
|
|
mesh.push_quad(create_ui_quad(
|
2019-03-20 05:13:42 +00:00
|
|
|
gl_aabr(rect),
|
|
|
|
Aabr {
|
|
|
|
min: Vec2::new(0.0, 0.0),
|
|
|
|
max: Vec2::new(0.0, 0.0),
|
|
|
|
},
|
2019-03-04 07:28:16 +00:00
|
|
|
color,
|
|
|
|
UiMode::Geometry,
|
2019-04-04 14:45:57 +00:00
|
|
|
));
|
2019-03-04 07:28:16 +00:00
|
|
|
}
|
|
|
|
PrimitiveKind::TrianglesSingleColor { color, triangles } => {
|
|
|
|
// Don't draw transparent triangle or switch state if there are actually no triangles
|
2019-04-19 18:55:38 +00:00
|
|
|
let color = srgb_to_linear(Rgba::from(Into::<[f32; 4]>::into(color)));
|
2019-03-04 07:28:16 +00:00
|
|
|
if triangles.is_empty() || color[3] == 0.0 {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch_to_plain_state!();
|
|
|
|
|
|
|
|
for tri in triangles {
|
2019-03-25 02:15:29 +00:00
|
|
|
let p1 = Vec2::new(vx(tri[0][0]), vy(tri[0][1]));
|
|
|
|
let p2 = Vec2::new(vx(tri[1][0]), vy(tri[1][1]));
|
|
|
|
let p3 = Vec2::new(vx(tri[2][0]), vy(tri[2][1]));
|
|
|
|
// If triangle is clockwise reverse it
|
2019-04-29 20:37:19 +00:00
|
|
|
let (v1, v2): (Vec3<f32>, Vec3<f32>) =
|
|
|
|
((p2 - p1).into(), (p3 - p1).into());
|
|
|
|
let triangle = if v1.cross(v2).z > 0.0 {
|
|
|
|
[p1.into_array(), p2.into_array(), p3.into_array()]
|
|
|
|
} else {
|
|
|
|
[p2.into_array(), p1.into_array(), p3.into_array()]
|
|
|
|
};
|
2019-04-04 14:45:57 +00:00
|
|
|
mesh.push_tri(create_ui_tri(
|
2019-03-04 07:28:16 +00:00
|
|
|
triangle,
|
|
|
|
[[0.0; 2]; 3],
|
2019-03-25 02:15:29 +00:00
|
|
|
color,
|
2019-03-04 07:28:16 +00:00
|
|
|
UiMode::Geometry,
|
2019-04-04 14:45:57 +00:00
|
|
|
));
|
2019-03-04 07:28:16 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-29 20:37:19 +00:00
|
|
|
_ => {} // TODO: Add this
|
|
|
|
//PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind multicolor with id {:?}", id);}
|
|
|
|
// Other uneeded for now
|
|
|
|
//PrimitiveKind::Other {..} => {println!("primitive kind other with id {:?}", id);}
|
2019-02-12 04:14:55 +00:00
|
|
|
}
|
|
|
|
}
|
2019-03-20 05:13:42 +00:00
|
|
|
// Enter the final command
|
2019-04-15 06:07:56 +00:00
|
|
|
self.draw_commands.push(match current_state {
|
2019-04-29 20:37:19 +00:00
|
|
|
State::Plain => DrawCommand::plain(renderer.create_model(&mesh).unwrap()),
|
|
|
|
State::Image => DrawCommand::image(renderer.create_model(&mesh).unwrap()),
|
2019-04-15 06:07:56 +00:00
|
|
|
});
|
2019-03-03 23:55:07 +00:00
|
|
|
|
|
|
|
// Handle window resizing
|
|
|
|
if let Some(new_dims) = self.window_resized.take() {
|
|
|
|
self.scale.window_resized(new_dims, renderer);
|
|
|
|
let (w, h) = self.scale.scaled_window_size().into_tuple();
|
|
|
|
self.ui.handle_event(Input::Resize(w, h));
|
2019-05-05 18:42:44 +00:00
|
|
|
|
|
|
|
let res = renderer.get_resolution();
|
|
|
|
// Avoid panic in graphic cache when minimizing
|
|
|
|
if res.x > 0 && res.y > 0 {
|
|
|
|
self.cache
|
|
|
|
.clear_graphic_cache(renderer, renderer.get_resolution().map(|e| e * 4));
|
|
|
|
}
|
2019-04-20 01:35:12 +00:00
|
|
|
// TODO: probably need to resize glyph cache, see conrod's gfx backend for reference
|
2019-03-03 23:55:07 +00:00
|
|
|
}
|
2019-02-12 04:14:55 +00:00
|
|
|
}
|
2019-01-30 12:11:34 +00:00
|
|
|
}
|
2019-02-16 03:01:42 +00:00
|
|
|
|
|
|
|
pub fn render(&self, renderer: &mut Renderer) {
|
2019-03-20 05:13:42 +00:00
|
|
|
let mut scissor = default_scissor(renderer);
|
2019-02-23 02:41:52 +00:00
|
|
|
for draw_command in self.draw_commands.iter() {
|
|
|
|
match draw_command {
|
2019-03-20 05:13:42 +00:00
|
|
|
DrawCommand::Scissor(scizzor) => {
|
|
|
|
scissor = *scizzor;
|
|
|
|
}
|
|
|
|
DrawCommand::Draw { kind, model } => {
|
|
|
|
let tex = match kind {
|
2019-04-29 20:37:19 +00:00
|
|
|
DrawKind::Image => self.cache.graphic_cache_tex(),
|
|
|
|
DrawKind::Plain => self.cache.glyph_cache_tex(),
|
2019-03-20 05:13:42 +00:00
|
|
|
};
|
|
|
|
renderer.render_ui_element(&model, &tex, scissor);
|
|
|
|
}
|
2019-02-16 03:01:42 +00:00
|
|
|
}
|
2019-02-23 02:41:52 +00:00
|
|
|
}
|
2019-02-16 03:01:42 +00:00
|
|
|
}
|
2019-01-30 12:11:34 +00:00
|
|
|
}
|
2019-03-20 05:13:42 +00:00
|
|
|
|
|
|
|
fn default_scissor(renderer: &mut Renderer) -> Aabr<u16> {
|
|
|
|
let (screen_w, screen_h) = renderer.get_resolution().map(|e| e as u16).into_tuple();
|
|
|
|
Aabr {
|
|
|
|
min: Vec2 { x: 0, y: 0 },
|
2019-04-29 20:37:19 +00:00
|
|
|
max: Vec2 {
|
|
|
|
x: screen_w,
|
|
|
|
y: screen_h,
|
|
|
|
},
|
2019-03-20 05:13:42 +00:00
|
|
|
}
|
2019-04-29 20:37:19 +00:00
|
|
|
}
|