#![doc(html_logo_url = "https://raw.githubusercontent.com/lowenware/dotrix/master/logo.png")]
#![warn(missing_docs)]
use dotrix_core::{ Application, Id, Pipeline, Assets, Input, Window };
use dotrix_core::assets::{ Mesh, Texture };
use dotrix_core::ecs::{ Mut, System };
use dotrix_core::input::{
Button,
Event as InputEvent,
KeyCode,
Modifiers,
State as InputState
};
use dotrix_overlay::{ Overlay, Ui, Widget };
pub use egui::*;
const TEXTURE_NAME: &str = "egui::texture";
#[derive(Default)]
pub struct Egui {
pub ctx: egui::CtxRef,
widgets: Vec<(Widget, Pipeline)>,
texture: Option<Id<Texture>>,
texture_version: Option<u64>,
scale_factor: f32,
surface_width: f32,
surface_height: f32,
}
impl Ui for Egui {
fn bind(
&mut self,
assets: &mut Assets,
input: &Input,
window: &Window,
) {
let scale_factor = window.scale_factor();
let surface_size = window.inner_size();
let surface_width = surface_size.x as f32;
let surface_height = surface_size.y as f32;
self.scale_factor = scale_factor;
self.surface_width = surface_width;
self.surface_height = surface_height;
let mut events = input.events.iter()
.map(|e| match e {
InputEvent::Copy => Some(egui::Event::Copy),
InputEvent::Cut => Some(egui::Event::Cut),
InputEvent::Text(text) => Some(egui::Event::Text(String::from(text))),
InputEvent::Key(event) => to_egui_key_code(event.key_code)
.map(|key| egui::Event::Key {
key,
pressed: event.pressed,
modifiers: to_egui_modifiers(event.modifiers),
}),
})
.flatten()
.collect::<Vec<_>>();
let mouse_pos = input.mouse_position()
.map(|p| egui::math::Pos2::new(p.x / scale_factor, p.y / scale_factor))
.unwrap_or_else(|| egui::math::Pos2::new(0.0, 0.0));
let dotrix_to_egui = [
(Button::MouseLeft, egui::PointerButton::Primary),
(Button::MouseRight, egui::PointerButton::Secondary),
(Button::MouseMiddle, egui::PointerButton::Middle),
];
for &(dotrix_button, egui_button) in dotrix_to_egui.iter() {
if let Some(button_state) = input.button_state(dotrix_button) {
events.push(egui::Event::PointerButton {
pos: mouse_pos,
button: egui_button,
pressed: button_state == InputState::Hold,
modifiers: Default::default(),
});
}
}
self.ctx.begin_frame(egui::RawInput {
screen_rect: Some(egui::math::Rect::from_min_size(
Default::default(),
egui::math::vec2(surface_width as f32, surface_height as f32) / scale_factor
)),
pixels_per_point: Some(scale_factor),
events,
..Default::default()
});
let texture = self.ctx.texture();
if let Some(texture_id) = self.texture {
if self.texture_version != Some(texture.version) {
let mut asset_texture = assets.get_mut::<Texture>(texture_id)
.expect("EGUI texture must be loaded already");
asset_texture.data = egui_texture_to_rgba(&texture);
asset_texture.width = texture.width as u32;
asset_texture.height = texture.height as u32;
asset_texture.unload();
} else {
return;
}
} else {
self.texture = Some(assets.store_as(Texture {
width: texture.width as u32,
height: texture.height as u32,
data: egui_texture_to_rgba(&texture),
depth: 1,
changed: true,
..Default::default()
}, TEXTURE_NAME));
}
self.texture_version = Some(texture.version);
}
fn tessellate(&mut self) -> &mut [(Widget, Pipeline)] {
let scale_factor = self.scale_factor;
let surface_width = self.surface_width;
let surface_height = self.surface_height;
let (_output, paint_commands) = self.ctx.end_frame();
let paint_jobs = self.ctx.tessellate(paint_commands);
let physical_width = surface_width * scale_factor;
let physical_height = surface_height * scale_factor;
self.widgets = paint_jobs.into_iter().map(|egui::ClippedMesh(clip_rect, egui_mesh)| {
let clip_min_x = scale_factor * clip_rect.min.x;
let clip_min_y = scale_factor * clip_rect.min.y;
let clip_max_x = scale_factor * clip_rect.max.x;
let clip_max_y = scale_factor * clip_rect.max.y;
let clip_min_x = clip_min_x.clamp(0.0, physical_width);
let clip_min_y = clip_min_y.clamp(0.0, physical_height);
let clip_max_x = clip_max_x.clamp(clip_min_x, physical_width);
let clip_max_y = clip_max_y.clamp(clip_min_y, physical_height);
let clip_min_x = clip_min_x.round() as u32;
let clip_min_y = clip_min_y.round() as u32;
let clip_max_x = clip_max_x.round() as u32;
let clip_max_y = clip_max_y.round() as u32;
let width = (clip_max_x - clip_min_x).max(1);
let height = (clip_max_y - clip_min_y).max(1);
let vertices_count = egui_mesh.vertices.len();
let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(vertices_count);
let mut colors: Vec<[f32; 4]> = Vec::with_capacity(vertices_count);
let positions = egui_mesh.vertices.iter().map(|v| {
uvs.push([v.uv.x, v.uv.y]);
colors.push([
v.color.r() as f32,
v.color.g() as f32,
v.color.b() as f32,
v.color.a() as f32 / 255.0,
]);
[v.pos.x, v.pos.y]
}).collect::<Vec<[f32; 2]>>();
let texture = match egui_mesh.texture_id {
TextureId::Egui => self.texture.expect("texture for widget should be loaded"),
TextureId::User(id) => Id::new(id),
};
let mut mesh = Mesh::default();
mesh.with_vertices(&positions);
mesh.with_vertices(&uvs);
mesh.with_vertices(&colors);
if !egui_mesh.indices.is_empty() {
mesh.with_indices(&egui_mesh.indices);
}
(
Widget {
mesh,
texture,
},
Pipeline::default()
.with_scissors_rect(
clip_min_x,
clip_min_y,
width,
height,
)
)
}).collect();
self.widgets.as_mut_slice()
}
}
fn egui_texture_to_rgba(texture: &egui::Texture) -> Vec<u8> {
let mut data = Vec::with_capacity(4 * texture.pixels.len());
for &alpha in texture.pixels.iter() {
data.extend(egui::paint::color::Color32::from_white_alpha(alpha).to_array().iter());
}
data
}
#[inline]
fn to_egui_key_code(key: KeyCode) -> Option<egui::Key> {
Some(match key {
KeyCode::Escape => Key::Escape,
KeyCode::Insert => Key::Insert,
KeyCode::Home => Key::Home,
KeyCode::Delete => Key::Delete,
KeyCode::End => Key::End,
KeyCode::PageDown => Key::PageDown,
KeyCode::PageUp => Key::PageUp,
KeyCode::Left => Key::ArrowLeft,
KeyCode::Up => Key::ArrowUp,
KeyCode::Right => Key::ArrowRight,
KeyCode::Down => Key::ArrowDown,
KeyCode::Back => Key::Backspace,
KeyCode::Return => Key::Enter,
KeyCode::Tab => Key::Tab,
KeyCode::Space => Key::Space,
KeyCode::A => Key::A,
KeyCode::K => Key::K,
KeyCode::U => Key::U,
KeyCode::W => Key::W,
KeyCode::Z => Key::Z,
_ => {
return None;
}
})
}
#[inline]
fn to_egui_modifiers(modifiers: Modifiers) -> egui::Modifiers {
egui::Modifiers {
alt: modifiers.alt(),
ctrl: modifiers.ctrl(),
shift: modifiers.shift(),
#[cfg(target_os = "macos")]
mac_cmd: modifiers.logo(),
#[cfg(target_os = "macos")]
command: modifiers.logo(),
#[cfg(not(target_os = "macos"))]
mac_cmd: false,
#[cfg(not(target_os = "macos"))]
command: modifiers.ctrl(),
}
}
pub fn startup(mut overlay: Mut<Overlay>) {
overlay.set(Egui::default());
}
pub fn extension(app: &mut Application) {
app.add_system(System::from(startup));
}