use std::{fmt::Debug, iter};
use egui::{
Align, Color32, Context, CursorIcon, Event, Frame, FullOutput, Image, Label, Layout, RawInput,
Stroke, UiBuilder,
};
use crate::{
gui_state::GuiState,
wgpu_wrapper::{WgpuSurface, WgpuWrapper},
};
pub struct Gui {
egui_ctx: Context,
egui_renderer: Option<egui_wgpu::Renderer>,
state: GuiState,
cursor_icon: CursorIcon,
}
impl Debug for Gui {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Gui").finish()
}
}
impl Default for Gui {
fn default() -> Self {
Self {
egui_ctx: Context::default(),
egui_renderer: None,
state: Default::default(),
cursor_icon: CursorIcon::Default,
}
}
}
impl Gui {
pub fn new() -> Self {
Gui::default()
}
pub fn add_item(&mut self, id: u32) {
self.state.add_item(id);
}
pub fn update_item_title(&mut self, id: u32, new_title: String) {
self.state.update_item_title(id, new_title);
}
pub fn update_item_app_id(&mut self, id: u32, new_app_id: String) {
self.state.update_item_app_id(id, new_app_id);
}
pub fn signal_item_activation(&mut self, id: u32) {
self.state.signal_item_activation(id);
}
pub fn remove_item(&mut self, id: u32) {
self.state.remove_item(id);
}
pub fn get_first_item_id(&self) -> Option<u32> {
self.state.get_first_item_id()
}
pub fn update_item_preview(&mut self, id: u32, preview_rgba: &[u8], preview_width: u32) {
self.state.update_item_preview(
id,
(preview_rgba, preview_width as usize),
|name, color_image| {
self.egui_ctx
.load_texture(name, color_image, Default::default())
},
);
}
pub fn reset_selected_item(&mut self) {
self.state.reset_selected_item();
}
pub fn get_selected_item_id(&self) -> Option<u32> {
self.state.get_selected_item_id()
}
pub fn calculate_preview_size(&self, current_size: (u32, u32)) -> (u32, u32) {
self.state.calculate_preview_size(current_size)
}
pub fn select_previous_item(&mut self) {
self.state.select_previous_item()
}
pub fn select_next_item(&mut self) {
self.state.select_next_item()
}
pub fn handle_events(&mut self, mut events: Vec<Event>) {
for event in &mut events {
if let Event::Key {
key: egui::Key::Tab,
pressed: true,
modifiers,
..
} = event
{
match modifiers.shift {
true => self.state.select_previous_item(),
false => self.state.select_next_item(),
}
}
}
let raw_input = RawInput {
events,
focused: true,
..Default::default()
};
self.build_ui(raw_input);
}
pub fn get_window_dimensions(&mut self) -> (u32, u32) {
let layout = self.state.calculate_layout();
(layout.computed.window_width, layout.computed.window_height)
}
fn build_ui(&mut self, raw_input: RawInput) -> FullOutput {
let layout = self.state.calculate_layout();
let mut hovered_item_updated = None;
let full_output = self.egui_ctx.run(raw_input, |ctx: &Context| {
let panel_frame = egui::Frame::new()
.fill(egui::Color32::from_rgba_unmultiplied(25, 25, 25, 230))
.corner_radius(layout.params.window_corner_radius);
egui::CentralPanel::default()
.frame(panel_frame)
.show(ctx, |ui| {
for (index, (rect, item)) in layout
.computed
.item_rects
.iter()
.zip(layout.items)
.enumerate()
{
let mut frame_ui = ui.new_child(UiBuilder::new().max_rect(*rect));
let mut frame = Frame::default()
.stroke(Stroke::new(
layout.params.item_stroke as f32,
Color32::TRANSPARENT,
))
.inner_margin(layout.params.item_padding as f32)
.corner_radius(layout.params.item_corner_radius)
.begin(&mut frame_ui);
{
let ui = &mut frame.content_ui;
ui.allocate_ui_with_layout(
(ui.available_width(), layout.params.title_height as f32).into(),
Layout::left_to_right(Align::Center),
|ui| ui.add(Label::new(item.get_title()).truncate()),
);
if let Some((handle, [width, height])) = item.get_preview() {
ui.add(
Image::from_texture((
handle.id(),
(*width as f32, *height as f32).into(),
))
.corner_radius(layout.params.preview_corner_radius),
);
} else {
ui.allocate_space(ui.available_size());
}
}
let response = frame.allocate_space(&mut frame_ui);
if response.hovered() {
hovered_item_updated = index.into();
}
if layout.selected_item == index {
frame.frame.stroke.color = Color32::WHITE;
frame.frame.fill = layout.params.item_active_background;
} else if let Some(hovered_item) = layout.hovered_item
&& hovered_item == index
{
frame.frame.fill = layout.params.item_hover_background;
}
frame.paint(&frame_ui);
}
});
});
self.cursor_icon = match hovered_item_updated {
Some(_) => CursorIcon::PointingHand,
None => CursorIcon::Default,
};
self.state.set_hovered_item(hovered_item_updated);
full_output
}
pub fn needs_repaint(&self) -> bool {
self.state.needs_repaint()
}
pub fn get_cursor_icon(&mut self) -> &CursorIcon {
&self.cursor_icon
}
pub fn paint(&mut self, wgpu: &mut WgpuWrapper, wsurf: &mut WgpuSurface) -> anyhow::Result<()> {
let _span = tracing::trace_span!("Paint").entered();
let output = wsurf.surface.get_current_texture()?;
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let width = wsurf.surface_config.width;
let height = wsurf.surface_config.height;
let raw_input = egui::RawInput {
screen_rect: Some(egui::Rect::from_min_size(
egui::Pos2::ZERO,
egui::vec2(width as f32, height as f32),
)),
focused: true,
..Default::default()
};
let full_output = self.build_ui(raw_input);
let mut encoder = wgpu
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
let screen_descriptor = egui_wgpu::ScreenDescriptor {
size_in_pixels: [width, height],
pixels_per_point: 1.0,
};
let clipped_primitives = self.egui_ctx.tessellate(full_output.shapes, 1.0);
let egui_renderer = self.egui_renderer.get_or_insert_with(|| {
egui_wgpu::Renderer::new(
&wgpu.device,
wsurf.surface_config.format,
egui_wgpu::RendererOptions::default(),
)
});
tracing::trace!("Updating textures");
for (id, image_delta) in &full_output.textures_delta.set {
egui_renderer.update_texture(&wgpu.device, &wgpu.queue, *id, image_delta);
}
tracing::trace!("Updating buffers");
egui_renderer.update_buffers(
&wgpu.device,
&wgpu.queue,
&mut encoder,
&clipped_primitives,
&screen_descriptor,
);
{
tracing::trace!("Beginning render pass");
let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
}),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
egui_renderer.render(
&mut render_pass.forget_lifetime(),
&clipped_primitives,
&screen_descriptor,
);
}
tracing::trace!("Freeing textures");
for id in &full_output.textures_delta.free {
egui_renderer.free_texture(id);
}
tracing::trace!("Submitting queue");
wgpu.queue.submit(iter::once(encoder.finish()));
tracing::trace!("Presenting output");
output.present();
tracing::trace!("Completed");
self.state.mark_repainted();
Ok(())
}
}