Update the demo for the D3D11 backend

This commit is contained in:
Patrick Walton 2020-06-23 13:08:49 -07:00
parent 72cdef2c2a
commit 423a91ea1a
3 changed files with 332 additions and 335 deletions

View File

@ -34,9 +34,10 @@ use pathfinder_geometry::transform2d::Transform2F;
use pathfinder_geometry::transform3d::Transform4F; use pathfinder_geometry::transform3d::Transform4F;
use pathfinder_geometry::vector::{Vector2F, Vector2I, Vector4F, vec2f, vec2i}; use pathfinder_geometry::vector::{Vector2F, Vector2I, Vector4F, vec2f, vec2i};
use pathfinder_gpu::Device; use pathfinder_gpu::Device;
use pathfinder_renderer::concurrent::scene_proxy::{RenderCommandStream, SceneProxy}; use pathfinder_renderer::concurrent::scene_proxy::SceneProxy;
use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererLevel};
use pathfinder_renderer::gpu::renderer::{RenderStats, RenderTime, Renderer}; use pathfinder_renderer::gpu::options::{RendererMode, RendererOptions};
use pathfinder_renderer::gpu::renderer::{DebugUIPresenterInfo, Renderer};
use pathfinder_renderer::options::{BuildOptions, RenderTransform}; use pathfinder_renderer::options::{BuildOptions, RenderTransform};
use pathfinder_renderer::paint::Paint; use pathfinder_renderer::paint::Paint;
use pathfinder_renderer::scene::{DrawPath, RenderTarget, Scene}; use pathfinder_renderer::scene::{DrawPath, RenderTarget, Scene};
@ -90,7 +91,6 @@ pub struct DemoApp<W> where W: Window {
svg_tree: Tree, svg_tree: Tree,
scene_metadata: SceneMetadata, scene_metadata: SceneMetadata,
render_transform: Option<RenderTransform>, render_transform: Option<RenderTransform>,
render_command_stream: Option<RenderCommandStream>,
camera: Camera, camera: Camera,
frame_counter: u32, frame_counter: u32,
@ -135,14 +135,25 @@ impl<W> DemoApp<W> where W: Window {
let executor = DemoExecutor::new(options.jobs); let executor = DemoExecutor::new(options.jobs);
let mut ui_model = DemoUIModel::new(&options); let mut ui_model = DemoUIModel::new(&options);
let level = match options.renderer_level {
Some(level) => level,
None => RendererLevel::default_for_device(&device),
};
let viewport = window.viewport(options.mode.view(0));
let dest_framebuffer = DestFramebuffer::Default {
viewport,
window_size: window_size.device_size(),
};
let render_mode = RendererMode { level };
let render_options = RendererOptions { let render_options = RendererOptions {
dest: dest_framebuffer,
background_color: None, background_color: None,
no_compute: options.no_compute, show_debug_ui: true,
}; };
let filter = build_filter(&ui_model); let filter = build_filter(&ui_model);
let viewport = window.viewport(options.mode.view(0));
let (mut built_svg, svg_tree) = load_scene(resources, let (mut built_svg, svg_tree) = load_scene(resources,
&options.input_path, &options.input_path,
viewport.size(), viewport.size(),
@ -150,21 +161,16 @@ impl<W> DemoApp<W> where W: Window {
let message = get_svg_building_message(&built_svg); let message = get_svg_building_message(&built_svg);
let dest_framebuffer = DestFramebuffer::Default { let renderer = Renderer::new(device, resources, render_mode, render_options);
viewport,
window_size: window_size.device_size(),
};
let renderer = Renderer::new(device, resources, dest_framebuffer, render_options);
let scene_metadata = SceneMetadata::new_clipping_view_box(&mut built_svg.scene, let scene_metadata = SceneMetadata::new_clipping_view_box(&mut built_svg.scene,
viewport.size()); viewport.size());
let camera = Camera::new(options.mode, scene_metadata.view_box, viewport.size()); let camera = Camera::new(options.mode, scene_metadata.view_box, viewport.size());
let scene_proxy = SceneProxy::from_scene(built_svg.scene, executor); let scene_proxy = SceneProxy::from_scene(built_svg.scene, level, executor);
let ground_program = GroundProgram::new(&renderer.device, resources); let ground_program = GroundProgram::new(renderer.device(), resources);
let ground_vertex_array = GroundVertexArray::new(&renderer.device, let ground_vertex_array = GroundVertexArray::new(renderer.device(),
&ground_program, &ground_program,
&renderer.quad_vertex_positions_buffer(), &renderer.quad_vertex_positions_buffer(),
&renderer.quad_vertex_indices_buffer()); &renderer.quad_vertex_indices_buffer());
@ -177,7 +183,7 @@ impl<W> DemoApp<W> where W: Window {
message, message,
); );
let ui_presenter = DemoUIPresenter::new(&renderer.device, resources); let ui_presenter = DemoUIPresenter::new(renderer.device(), resources);
DemoApp { DemoApp {
window, window,
@ -189,7 +195,6 @@ impl<W> DemoApp<W> where W: Window {
svg_tree, svg_tree,
scene_metadata, scene_metadata,
render_transform: None, render_transform: None,
render_command_stream: None,
camera, camera,
frame_counter: 0, frame_counter: 0,
@ -265,7 +270,11 @@ impl<W> DemoApp<W> where W: Window {
subpixel_aa_enabled: self.ui_model.subpixel_aa_effect_enabled, subpixel_aa_enabled: self.ui_model.subpixel_aa_effect_enabled,
}; };
self.render_command_stream = Some(self.scene_proxy.build_with_stream(build_options)); self.scene_proxy.build(build_options);
/*
self.render_command_stream =
Some(self.scene_proxy.build_with_stream(build_options, self.renderer.gpu_features()));
*/
} }
fn handle_events(&mut self, events: Vec<Event>) -> Vec<UIEvent> { fn handle_events(&mut self, events: Vec<Event>) -> Vec<UIEvent> {
@ -469,57 +478,41 @@ impl<W> DemoApp<W> where W: Window {
pub fn finish_drawing_frame(&mut self) { pub fn finish_drawing_frame(&mut self) {
self.maybe_take_screenshot(); self.maybe_take_screenshot();
self.update_stats();
self.draw_debug_ui();
let frame = self.current_frame.take().unwrap(); let frame = self.current_frame.take().unwrap();
for ui_event in &frame.ui_events { for ui_event in &frame.ui_events {
self.dirty = true; self.dirty = true;
self.renderer.debug_ui_presenter.ui_presenter.event_queue.push(*ui_event); self.renderer
.debug_ui_presenter_mut()
.debug_ui_presenter
.ui_presenter
.event_queue
.push(*ui_event);
} }
self.renderer.debug_ui_presenter.ui_presenter.mouse_position = self.renderer.debug_ui_presenter_mut().debug_ui_presenter.ui_presenter.mouse_position =
self.last_mouse_position.to_f32() * self.window_size.backing_scale_factor; self.last_mouse_position.to_f32() * self.window_size.backing_scale_factor;
let mut ui_action = UIAction::None; let mut ui_action = UIAction::None;
if self.options.ui == UIVisibility::All { if self.options.ui == UIVisibility::All {
self.ui_presenter.update( let DebugUIPresenterInfo { device, allocator, debug_ui_presenter } =
&self.renderer.device, self.renderer.debug_ui_presenter_mut();
&mut self.window, self.ui_presenter.update(device,
&mut self.renderer.debug_ui_presenter, allocator,
&mut ui_action, &mut self.window,
&mut self.ui_model, debug_ui_presenter,
); &mut ui_action,
&mut self.ui_model);
} }
self.handle_ui_events(frame, &mut ui_action); self.handle_ui_events(frame, &mut ui_action);
self.renderer.device.end_commands(); self.renderer.device().end_commands();
self.window.present(&mut self.renderer.device); self.window.present(self.renderer.device_mut());
self.frame_counter += 1; self.frame_counter += 1;
} }
fn update_stats(&mut self) {
let frame = self.current_frame.as_mut().unwrap();
if let Some(rendering_time) = self.renderer.shift_rendering_time() {
frame.scene_rendering_times.push(rendering_time);
}
if frame.scene_stats.is_empty() && frame.scene_rendering_times.is_empty() {
return
}
let zero = RenderStats::default();
let aggregate_stats = frame.scene_stats.iter().fold(zero, |sum, item| sum + *item);
if !frame.scene_rendering_times.is_empty() {
let total_rendering_time = frame.scene_rendering_times
.iter()
.fold(RenderTime::default(), |sum, item| sum + *item);
self.renderer.debug_ui_presenter.add_sample(aggregate_stats, total_rendering_time);
}
}
fn maybe_take_screenshot(&mut self) { fn maybe_take_screenshot(&mut self) {
match self.pending_screenshot_info.take() { match self.pending_screenshot_info.take() {
None => {} None => {}
@ -535,7 +528,12 @@ impl<W> DemoApp<W> where W: Window {
} }
fn handle_ui_events(&mut self, mut frame: Frame, ui_action: &mut UIAction) { fn handle_ui_events(&mut self, mut frame: Frame, ui_action: &mut UIAction) {
frame.ui_events = self.renderer.debug_ui_presenter.ui_presenter.event_queue.drain(); frame.ui_events = self.renderer
.debug_ui_presenter_mut()
.debug_ui_presenter
.ui_presenter
.event_queue
.drain();
self.handle_ui_action(ui_action); self.handle_ui_action(ui_action);
@ -625,7 +623,7 @@ pub struct Options {
pub ui: UIVisibility, pub ui: UIVisibility,
pub background_color: BackgroundColor, pub background_color: BackgroundColor,
pub high_performance_gpu: bool, pub high_performance_gpu: bool,
pub no_compute: bool, pub renderer_level: Option<RendererLevel>,
hidden_field_for_future_proofing: (), hidden_field_for_future_proofing: (),
} }
@ -638,7 +636,7 @@ impl Default for Options {
ui: UIVisibility::All, ui: UIVisibility::All,
background_color: BackgroundColor::Light, background_color: BackgroundColor::Light,
high_performance_gpu: false, high_performance_gpu: false,
no_compute: false, renderer_level: None,
hidden_field_for_future_proofing: (), hidden_field_for_future_proofing: (),
} }
} }
@ -646,7 +644,7 @@ impl Default for Options {
impl Options { impl Options {
pub fn command_line_overrides(&mut self) { pub fn command_line_overrides(&mut self) {
let matches = App::new("tile-svg") let matches = App::new("demo")
.arg( .arg(
Arg::with_name("jobs") Arg::with_name("jobs")
.short("j") .short("j")
@ -692,10 +690,12 @@ impl Options {
.help("Use the high-performance (discrete) GPU, if available") .help("Use the high-performance (discrete) GPU, if available")
) )
.arg( .arg(
Arg::with_name("no-compute") Arg::with_name("level")
.short("c") .long("level")
.long("no-compute") .short("l")
.help("Never use compute shaders") .help("Set the renderer feature level as a Direct3D version equivalent")
.takes_value(true)
.possible_values(&["9", "11"])
) )
.arg( .arg(
Arg::with_name("INPUT") Arg::with_name("INPUT")
@ -734,13 +734,17 @@ impl Options {
self.high_performance_gpu = true; self.high_performance_gpu = true;
} }
if matches.is_present("no-compute") { if let Some(renderer_level) = matches.value_of("level") {
self.no_compute = true; if renderer_level == "11" {
self.renderer_level = Some(RendererLevel::D3D11);
} else if renderer_level == "9" {
self.renderer_level = Some(RendererLevel::D3D9);
}
} }
if let Some(path) = matches.value_of("INPUT") { if let Some(path) = matches.value_of("INPUT") {
self.input_path = SVGPath::Path(PathBuf::from(path)); self.input_path = SVGPath::Path(PathBuf::from(path));
}; }
} }
} }
@ -798,7 +802,7 @@ fn build_svg_tree(tree: &Tree, viewport_size: Vector2I, filter: Option<PatternFi
let path = DrawPath::new(outline, paint_id); let path = DrawPath::new(outline, paint_id);
built_svg.scene.pop_render_target(); built_svg.scene.pop_render_target();
built_svg.scene.push_path(path); built_svg.scene.push_draw_path(path);
} }
return built_svg; return built_svg;
@ -848,18 +852,11 @@ fn emit_message<W>(
struct Frame { struct Frame {
transform: RenderTransform, transform: RenderTransform,
ui_events: Vec<UIEvent>, ui_events: Vec<UIEvent>,
scene_rendering_times: Vec<RenderTime>,
scene_stats: Vec<RenderStats>,
} }
impl Frame { impl Frame {
fn new(transform: RenderTransform, ui_events: Vec<UIEvent>) -> Frame { fn new(transform: RenderTransform, ui_events: Vec<UIEvent>) -> Frame {
Frame { Frame { transform, ui_events }
transform,
ui_events,
scene_rendering_times: vec![],
scene_stats: vec![],
}
} }
} }

View File

@ -22,6 +22,7 @@ use pathfinder_geometry::transform3d::Transform4F;
use pathfinder_geometry::vector::{Vector2I, Vector4F}; use pathfinder_geometry::vector::{Vector2I, Vector4F};
use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions};
use pathfinder_renderer::options::RenderTransform; use pathfinder_renderer::options::RenderTransform;
use std::mem;
use std::path::PathBuf; use std::path::PathBuf;
const GROUND_SOLID_COLOR: ColorU = ColorU { const GROUND_SOLID_COLOR: ColorU = ColorU {
@ -46,59 +47,58 @@ impl<W> DemoApp<W> where W: Window {
let view = self.ui_model.mode.view(0); let view = self.ui_model.mode.view(0);
self.window.make_current(view); self.window.make_current(view);
// Set up framebuffers.
let window_size = self.window_size.device_size();
let mode = self.camera.mode();
let scene_count = match mode {
Mode::VR => {
let viewport = self.window.viewport(View::Stereo(0));
if self.scene_framebuffer.is_none()
|| self.renderer.device.texture_size(
&self
.renderer
.device
.framebuffer_texture(self.scene_framebuffer.as_ref().unwrap()),
) != viewport.size()
{
let scene_texture = self
.renderer
.device
.create_texture(TextureFormat::RGBA8, viewport.size());
self.scene_framebuffer =
Some(self.renderer.device.create_framebuffer(scene_texture));
}
self.renderer
.replace_dest_framebuffer(DestFramebuffer::Other(
self.scene_framebuffer.take().unwrap(),
));
2
}
_ => {
self.renderer
.replace_dest_framebuffer(DestFramebuffer::Default {
viewport: self.window.viewport(View::Mono),
window_size,
});
1
}
};
// Clear to the appropriate color. // Clear to the appropriate color.
let mode = self.camera.mode();
let clear_color = match mode { let clear_color = match mode {
Mode::TwoD => Some(self.ui_model.background_color().to_f32()), Mode::TwoD => Some(self.ui_model.background_color().to_f32()),
Mode::ThreeD => None, Mode::ThreeD => None,
Mode::VR => Some(ColorF::transparent_black()), Mode::VR => Some(ColorF::transparent_black()),
}; };
self.renderer.set_options(RendererOptions {
background_color: clear_color, // Set up framebuffers.
no_compute: self.options.no_compute, let window_size = self.window_size.device_size();
}); let scene_count = match mode {
Mode::VR => {
let viewport = self.window.viewport(View::Stereo(0));
if self.scene_framebuffer.is_none()
|| self.renderer.device().texture_size(
&self.renderer.device().framebuffer_texture(self.scene_framebuffer
.as_ref()
.unwrap()),
) != viewport.size()
{
let scene_texture = self
.renderer
.device()
.create_texture(TextureFormat::RGBA8, viewport.size());
self.scene_framebuffer =
Some(self.renderer.device().create_framebuffer(scene_texture));
}
*self.renderer.options_mut() = RendererOptions {
dest: DestFramebuffer::Other(self.scene_framebuffer.take().unwrap()),
background_color: clear_color,
show_debug_ui: self.options.ui != UIVisibility::None,
};
2
}
_ => {
*self.renderer.options_mut() = RendererOptions {
dest: DestFramebuffer::Default {
viewport: self.window.viewport(View::Mono),
window_size,
},
background_color: clear_color,
show_debug_ui: self.options.ui != UIVisibility::None,
};
1
}
};
scene_count scene_count
} }
pub fn draw_scene(&mut self) { pub fn draw_scene(&mut self) {
self.renderer.device.begin_commands(); self.renderer.device().begin_commands();
let view = self.ui_model.mode.view(0); let view = self.ui_model.mode.view(0);
self.window.make_current(view); self.window.make_current(view);
@ -107,26 +107,29 @@ impl<W> DemoApp<W> where W: Window {
self.draw_environment(0); self.draw_environment(0);
} }
self.renderer.device.end_commands(); self.renderer.device().end_commands();
self.render_vector_scene(); self.render_vector_scene();
// Reattach default framebuffer. // Reattach default framebuffer.
if self.camera.mode() == Mode::VR { if self.camera.mode() == Mode::VR {
if let DestFramebuffer::Other(scene_framebuffer) = let new_options = RendererOptions {
self.renderer dest: DestFramebuffer::Default {
.replace_dest_framebuffer(DestFramebuffer::Default { viewport: self.window.viewport(View::Mono),
viewport: self.window.viewport(View::Mono), window_size: self.window_size.device_size(),
window_size: self.window_size.device_size(), },
}) ..*self.renderer.options()
{ };
if let DestFramebuffer::Other(scene_framebuffer) = mem::replace(self.renderer
.options_mut(),
new_options).dest {
self.scene_framebuffer = Some(scene_framebuffer); self.scene_framebuffer = Some(scene_framebuffer);
} }
} }
} }
pub fn begin_compositing(&mut self) { pub fn begin_compositing(&mut self) {
self.renderer.device.begin_commands(); self.renderer.device().begin_commands();
} }
pub fn composite_scene(&mut self, render_scene_index: u32) { pub fn composite_scene(&mut self, render_scene_index: u32) {
@ -153,15 +156,15 @@ impl<W> DemoApp<W> where W: Window {
let viewport = self.window.viewport(View::Stereo(render_scene_index)); let viewport = self.window.viewport(View::Stereo(render_scene_index));
self.window.make_current(View::Stereo(render_scene_index)); self.window.make_current(View::Stereo(render_scene_index));
self.renderer.replace_dest_framebuffer(DestFramebuffer::Default { self.renderer.options_mut().dest = DestFramebuffer::Default {
viewport, viewport,
window_size: self.window_size.device_size(), window_size: self.window_size.device_size(),
}); };
self.draw_environment(render_scene_index); self.draw_environment(render_scene_index);
let scene_framebuffer = self.scene_framebuffer.as_ref().unwrap(); let scene_framebuffer = self.scene_framebuffer.as_ref().unwrap();
let scene_texture = self.renderer.device.framebuffer_texture(scene_framebuffer); let scene_texture = self.renderer.device().framebuffer_texture(scene_framebuffer);
let mut quad_scale = self.scene_metadata.view_box.size().to_4d(); let mut quad_scale = self.scene_metadata.view_box.size().to_4d();
quad_scale.set_z(1.0); quad_scale.set_z(1.0);
@ -226,13 +229,14 @@ impl<W> DemoApp<W> where W: Window {
None None
}; };
self.renderer.device.draw_elements(6, &RenderState { self.renderer.device().draw_elements(6, &RenderState {
target: &self.renderer.draw_render_target(), target: &self.renderer.draw_render_target(),
program: &self.ground_program.program, program: &self.ground_program.program,
vertex_array: &self.ground_vertex_array.vertex_array, vertex_array: &self.ground_vertex_array.vertex_array,
primitive: Primitive::Triangles, primitive: Primitive::Triangles,
textures: &[], textures: &[],
images: &[], images: &[],
storage_buffers: &[],
uniforms: &[ uniforms: &[
(&self.ground_program.transform_uniform, (&self.ground_program.transform_uniform,
UniformData::from_transform_3d(&transform)), UniformData::from_transform_3d(&transform)),
@ -258,27 +262,16 @@ impl<W> DemoApp<W> where W: Window {
self.renderer.enable_depth(); self.renderer.enable_depth();
} }
self.renderer.begin_scene();
// Issue render commands! // Issue render commands!
for command in self.render_command_stream.as_mut().unwrap() { self.scene_proxy.render(&mut self.renderer);
self.renderer.render_command(&command);
}
self.current_frame
.as_mut()
.unwrap()
.scene_stats
.push(self.renderer.stats);
self.renderer.end_scene();
} }
pub fn take_raster_screenshot(&mut self, path: PathBuf) { pub fn take_raster_screenshot(&mut self, path: PathBuf) {
let drawable_size = self.window_size.device_size(); let drawable_size = self.window_size.device_size();
let viewport = RectI::new(Vector2I::default(), drawable_size); let viewport = RectI::new(Vector2I::default(), drawable_size);
let texture_data_receiver = let texture_data_receiver =
self.renderer.device.read_pixels(&RenderTarget::Default, viewport); self.renderer.device().read_pixels(&RenderTarget::Default, viewport);
let pixels = match self.renderer.device.recv_texture_data(&texture_data_receiver) { let pixels = match self.renderer.device().recv_texture_data(&texture_data_receiver) {
TextureData::U8(pixels) => pixels, TextureData::U8(pixels) => pixels,
_ => panic!("Unexpected pixel format for default framebuffer!"), _ => panic!("Unexpected pixel format for default framebuffer!"),
}; };
@ -291,19 +284,4 @@ impl<W> DemoApp<W> where W: Window {
) )
.unwrap(); .unwrap();
} }
pub fn draw_debug_ui(&mut self) {
if self.options.ui == UIVisibility::None {
return;
}
let viewport = self.window.viewport(View::Mono);
self.window.make_current(View::Mono);
self.renderer.replace_dest_framebuffer(DestFramebuffer::Default {
viewport,
window_size: self.window_size.device_size(),
});
self.renderer.draw_debug_ui();
}
} }

View File

@ -14,6 +14,7 @@ use crate::{BackgroundColor, Options};
use pathfinder_color::ColorU; use pathfinder_color::ColorU;
use pathfinder_geometry::rect::RectI; use pathfinder_geometry::rect::RectI;
use pathfinder_geometry::vector::{Vector2I, vec2i}; use pathfinder_geometry::vector::{Vector2I, vec2i};
use pathfinder_gpu::allocator::GPUMemoryAllocator;
use pathfinder_gpu::{Device, TextureFormat}; use pathfinder_gpu::{Device, TextureFormat};
use pathfinder_renderer::gpu::debug::DebugUIPresenter; use pathfinder_renderer::gpu::debug::DebugUIPresenter;
use pathfinder_resources::ResourceLoader; use pathfinder_resources::ResourceLoader;
@ -97,10 +98,7 @@ impl DemoUIModel {
} }
} }
pub struct DemoUIPresenter<D> pub struct DemoUIPresenter<D> where D: Device {
where
D: Device,
{
effects_texture: D::Texture, effects_texture: D::Texture,
open_texture: D::Texture, open_texture: D::Texture,
rotate_texture: D::Texture, rotate_texture: D::Texture,
@ -116,11 +114,10 @@ where
rotate_panel_visible: bool, rotate_panel_visible: bool,
} }
impl<D> DemoUIPresenter<D> impl<D> DemoUIPresenter<D> where D: Device {
where
D: Device,
{
pub fn new(device: &D, resources: &dyn ResourceLoader) -> DemoUIPresenter<D> { pub fn new(device: &D, resources: &dyn ResourceLoader) -> DemoUIPresenter<D> {
device.begin_commands();
let effects_texture = device.create_texture_from_png(resources, let effects_texture = device.create_texture_from_png(resources,
EFFECTS_PNG_NAME, EFFECTS_PNG_NAME,
TextureFormat::R8); TextureFormat::R8);
@ -146,6 +143,8 @@ where
SCREENSHOT_PNG_NAME, SCREENSHOT_PNG_NAME,
TextureFormat::R8); TextureFormat::R8);
device.end_commands();
DemoUIPresenter { DemoUIPresenter {
effects_texture, effects_texture,
open_texture, open_texture,
@ -163,19 +162,17 @@ where
} }
} }
pub fn update<W>( pub fn update<W>(&mut self,
&mut self, device: &D,
device: &D, allocator: &mut GPUMemoryAllocator<D>,
window: &mut W, window: &mut W,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
action: &mut UIAction, action: &mut UIAction,
model: &mut DemoUIModel model: &mut DemoUIModel)
) where where W: Window {
W: Window,
{
// Draw message text. // Draw message text.
self.draw_message_text(device, debug_ui_presenter, model); self.draw_message_text(device, allocator, debug_ui_presenter, model);
// Draw button strip. // Draw button strip.
@ -185,53 +182,60 @@ where
let button_size = vec2i(BUTTON_WIDTH, BUTTON_HEIGHT); let button_size = vec2i(BUTTON_WIDTH, BUTTON_HEIGHT);
// Draw effects button. // Draw effects button.
if debug_ui_presenter.ui_presenter.draw_button(device, position, &self.effects_texture) { if debug_ui_presenter.ui_presenter
.draw_button(device, allocator, position, &self.effects_texture) {
self.effects_panel_visible = !self.effects_panel_visible; self.effects_panel_visible = !self.effects_panel_visible;
} }
if !self.effects_panel_visible { if !self.effects_panel_visible {
debug_ui_presenter.ui_presenter.draw_tooltip( debug_ui_presenter.ui_presenter.draw_tooltip(device,
device, allocator,
"Effects", "Effects",
RectI::new(position, button_size), RectI::new(position, button_size));
);
} }
position += vec2i(button_size.x() + PADDING, 0); position += vec2i(button_size.x() + PADDING, 0);
// Draw open button. // Draw open button.
if debug_ui_presenter.ui_presenter.draw_button(device, position, &self.open_texture) { if debug_ui_presenter.ui_presenter
.draw_button(device, allocator, position, &self.open_texture) {
// FIXME(pcwalton): This is not sufficient for Android, where we will need to take in // FIXME(pcwalton): This is not sufficient for Android, where we will need to take in
// the contents of the file. // the contents of the file.
window.present_open_svg_dialog(); window.present_open_svg_dialog();
} }
debug_ui_presenter.ui_presenter.draw_tooltip(device, debug_ui_presenter.ui_presenter.draw_tooltip(device,
allocator,
"Open SVG", "Open SVG",
RectI::new(position, button_size)); RectI::new(position, button_size));
position += vec2i(BUTTON_WIDTH + PADDING, 0); position += vec2i(BUTTON_WIDTH + PADDING, 0);
// Draw screenshot button. // Draw screenshot button.
if debug_ui_presenter.ui_presenter.draw_button(device, if debug_ui_presenter.ui_presenter
position, .draw_button(device, allocator, position, &self.screenshot_texture) {
&self.screenshot_texture) {
self.screenshot_panel_visible = !self.screenshot_panel_visible; self.screenshot_panel_visible = !self.screenshot_panel_visible;
} }
if !self.screenshot_panel_visible { if !self.screenshot_panel_visible {
debug_ui_presenter.ui_presenter.draw_tooltip( debug_ui_presenter.ui_presenter.draw_tooltip(
device, device,
allocator,
"Take Screenshot", "Take Screenshot",
RectI::new(position, button_size), RectI::new(position, button_size),
); );
} }
// Draw screenshot panel, if necessary. // Draw screenshot panel, if necessary.
self.draw_screenshot_panel(device, window, debug_ui_presenter, position.x(), action); self.draw_screenshot_panel(device,
allocator,
window,
debug_ui_presenter,
position.x(),
action);
position += vec2i(button_size.x() + PADDING, 0); position += vec2i(button_size.x() + PADDING, 0);
// Draw mode switch. // Draw mode switch.
let new_mode = debug_ui_presenter.ui_presenter.draw_text_switch( let new_mode = debug_ui_presenter.ui_presenter.draw_text_switch(device,
device, allocator,
position, position,
&["2D", "3D", "VR"], &["2D", "3D", "VR"],
model.mode as u8); model.mode as u8);
if new_mode != model.mode as u8 { if new_mode != model.mode as u8 {
model.mode = match new_mode { model.mode = match new_mode {
0 => Mode::TwoD, 0 => Mode::TwoD,
@ -245,6 +249,7 @@ where
let mode_switch_size = vec2i(mode_switch_width, BUTTON_HEIGHT); let mode_switch_size = vec2i(mode_switch_width, BUTTON_HEIGHT);
debug_ui_presenter.ui_presenter.draw_tooltip( debug_ui_presenter.ui_presenter.draw_tooltip(
device, device,
allocator,
"2D/3D/VR Mode", "2D/3D/VR Mode",
RectI::new(position, mode_switch_size), RectI::new(position, mode_switch_size),
); );
@ -252,6 +257,7 @@ where
// Draw background switch. // Draw background switch.
if debug_ui_presenter.ui_presenter.draw_button(device, if debug_ui_presenter.ui_presenter.draw_button(device,
allocator,
position, position,
&self.background_texture) { &self.background_texture) {
self.background_panel_visible = !self.background_panel_visible; self.background_panel_visible = !self.background_panel_visible;
@ -259,50 +265,60 @@ where
if !self.background_panel_visible { if !self.background_panel_visible {
debug_ui_presenter.ui_presenter.draw_tooltip( debug_ui_presenter.ui_presenter.draw_tooltip(
device, device,
allocator,
"Background Color", "Background Color",
RectI::new(position, button_size), RectI::new(position, button_size),
); );
} }
// Draw background panel, if necessary. // Draw background panel, if necessary.
self.draw_background_panel(device, debug_ui_presenter, position.x(), action, model); self.draw_background_panel(device,
allocator,
debug_ui_presenter,
position.x(),
action,
model);
position += vec2i(button_size.x() + PADDING, 0); position += vec2i(button_size.x() + PADDING, 0);
// Draw effects panel, if necessary. // Draw effects panel, if necessary.
self.draw_effects_panel(device, debug_ui_presenter, model, action); self.draw_effects_panel(device, allocator, debug_ui_presenter, model, action);
// Draw rotate and zoom buttons, if applicable. // Draw rotate and zoom buttons, if applicable.
if model.mode != Mode::TwoD { if model.mode != Mode::TwoD {
return; return;
} }
if debug_ui_presenter.ui_presenter.draw_button(device, position, &self.rotate_texture) { if debug_ui_presenter.ui_presenter.draw_button(device,
allocator,
position,
&self.rotate_texture) {
self.rotate_panel_visible = !self.rotate_panel_visible; self.rotate_panel_visible = !self.rotate_panel_visible;
} }
if !self.rotate_panel_visible { if !self.rotate_panel_visible {
debug_ui_presenter.ui_presenter.draw_tooltip(device, debug_ui_presenter.ui_presenter.draw_tooltip(device,
allocator,
"Rotate", "Rotate",
RectI::new(position, button_size)); RectI::new(position, button_size));
} }
self.draw_rotate_panel(device, debug_ui_presenter, position.x(), action, model); self.draw_rotate_panel(device, allocator, debug_ui_presenter, position.x(), action, model);
position += vec2i(BUTTON_WIDTH + PADDING, 0); position += vec2i(BUTTON_WIDTH + PADDING, 0);
// Draw zoom control. // Draw zoom control.
self.draw_zoom_control(device, debug_ui_presenter, position, action); self.draw_zoom_control(device, allocator, debug_ui_presenter, position, action);
} }
fn draw_zoom_control( fn draw_zoom_control(&mut self,
&mut self, device: &D,
device: &D, allocator: &mut GPUMemoryAllocator<D>,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
position: Vector2I, position: Vector2I,
action: &mut UIAction, action: &mut UIAction) {
) {
let zoom_segmented_control_width = let zoom_segmented_control_width =
debug_ui_presenter.ui_presenter.measure_segmented_control(3); debug_ui_presenter.ui_presenter.measure_segmented_control(3);
let zoom_segmented_control_rect = let zoom_segmented_control_rect =
RectI::new(position, vec2i(zoom_segmented_control_width, BUTTON_HEIGHT)); RectI::new(position, vec2i(zoom_segmented_control_width, BUTTON_HEIGHT));
debug_ui_presenter.ui_presenter.draw_tooltip(device, "Zoom", zoom_segmented_control_rect); debug_ui_presenter.ui_presenter
.draw_tooltip(device, allocator, "Zoom", zoom_segmented_control_rect);
let zoom_textures = &[ let zoom_textures = &[
&self.zoom_in_texture, &self.zoom_in_texture,
@ -311,6 +327,7 @@ where
]; ];
match debug_ui_presenter.ui_presenter.draw_image_segmented_control(device, match debug_ui_presenter.ui_presenter.draw_image_segmented_control(device,
allocator,
position, position,
zoom_textures, zoom_textures,
None) { None) {
@ -323,6 +340,7 @@ where
fn draw_message_text(&mut self, fn draw_message_text(&mut self,
device: &D, device: &D,
allocator: &mut GPUMemoryAllocator<D>,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
model: &mut DemoUIModel) { model: &mut DemoUIModel) {
if model.message.is_empty() { if model.message.is_empty() {
@ -334,11 +352,13 @@ where
let window_size = vec2i(PADDING * 2 + message_size, TOOLTIP_HEIGHT); let window_size = vec2i(PADDING * 2 + message_size, TOOLTIP_HEIGHT);
debug_ui_presenter.ui_presenter.draw_solid_rounded_rect( debug_ui_presenter.ui_presenter.draw_solid_rounded_rect(
device, device,
allocator,
RectI::new(window_origin, window_size), RectI::new(window_origin, window_size),
WINDOW_COLOR, WINDOW_COLOR,
); );
debug_ui_presenter.ui_presenter.draw_text( debug_ui_presenter.ui_presenter.draw_text(
device, device,
allocator,
&model.message, &model.message,
window_origin + vec2i(PADDING, PADDING + FONT_ASCENT), window_origin + vec2i(PADDING, PADDING + FONT_ASCENT),
false, false,
@ -347,6 +367,7 @@ where
fn draw_effects_panel(&mut self, fn draw_effects_panel(&mut self,
device: &D, device: &D,
allocator: &mut GPUMemoryAllocator<D>,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
model: &mut DemoUIModel, model: &mut DemoUIModel,
action: &mut UIAction) { action: &mut UIAction) {
@ -358,45 +379,45 @@ where
let effects_panel_y = bottom - (BUTTON_HEIGHT + PADDING + EFFECTS_PANEL_HEIGHT); let effects_panel_y = bottom - (BUTTON_HEIGHT + PADDING + EFFECTS_PANEL_HEIGHT);
debug_ui_presenter.ui_presenter.draw_solid_rounded_rect( debug_ui_presenter.ui_presenter.draw_solid_rounded_rect(
device, device,
allocator,
RectI::new(vec2i(PADDING, effects_panel_y), RectI::new(vec2i(PADDING, effects_panel_y),
vec2i(EFFECTS_PANEL_WIDTH, EFFECTS_PANEL_HEIGHT)), vec2i(EFFECTS_PANEL_WIDTH, EFFECTS_PANEL_HEIGHT)),
WINDOW_COLOR, WINDOW_COLOR);
);
self.draw_effects_switch( self.draw_effects_switch(device,
device, allocator,
action, action,
debug_ui_presenter, debug_ui_presenter,
"Gamma Correction", "Gamma Correction",
0, 0,
effects_panel_y, effects_panel_y,
&mut model.gamma_correction_effect_enabled); &mut model.gamma_correction_effect_enabled);
self.draw_effects_switch( self.draw_effects_switch(device,
device, allocator,
action, action,
debug_ui_presenter, debug_ui_presenter,
"Stem Darkening", "Stem Darkening",
1, 1,
effects_panel_y, effects_panel_y,
&mut model.stem_darkening_effect_enabled); &mut model.stem_darkening_effect_enabled);
self.draw_effects_switch( self.draw_effects_switch(device,
device, allocator,
action, action,
debug_ui_presenter, debug_ui_presenter,
"Subpixel AA", "Subpixel AA",
2, 2,
effects_panel_y, effects_panel_y,
&mut model.subpixel_aa_effect_enabled); &mut model.subpixel_aa_effect_enabled);
} }
fn draw_screenshot_panel<W>( fn draw_screenshot_panel<W>(&mut self,
&mut self, device: &D,
device: &D, allocator: &mut GPUMemoryAllocator<D>,
window: &mut W, window: &mut W,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
panel_x: i32, panel_x: i32,
action: &mut UIAction, action: &mut UIAction)
) where W: Window { where W: Window {
if !self.screenshot_panel_visible { if !self.screenshot_panel_visible {
return; return;
} }
@ -406,36 +427,34 @@ where
let panel_position = vec2i(panel_x, panel_y); let panel_position = vec2i(panel_x, panel_y);
debug_ui_presenter.ui_presenter.draw_solid_rounded_rect( debug_ui_presenter.ui_presenter.draw_solid_rounded_rect(
device, device,
allocator,
RectI::new(panel_position, vec2i(SCREENSHOT_PANEL_WIDTH, SCREENSHOT_PANEL_HEIGHT)), RectI::new(panel_position, vec2i(SCREENSHOT_PANEL_WIDTH, SCREENSHOT_PANEL_HEIGHT)),
WINDOW_COLOR, WINDOW_COLOR,
); );
self.draw_screenshot_menu_item( self.draw_screenshot_menu_item(device,
device, allocator,
window, window,
debug_ui_presenter, debug_ui_presenter,
ScreenshotType::PNG, ScreenshotType::PNG,
panel_position, panel_position,
action, action);
); self.draw_screenshot_menu_item(device,
self.draw_screenshot_menu_item( allocator,
device, window,
window, debug_ui_presenter,
debug_ui_presenter, ScreenshotType::SVG,
ScreenshotType::SVG, panel_position,
panel_position, action);
action,
);
} }
fn draw_background_panel( fn draw_background_panel(&mut self,
&mut self, device: &D,
device: &D, allocator: &mut GPUMemoryAllocator<D>,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
panel_x: i32, panel_x: i32,
action: &mut UIAction, action: &mut UIAction,
model: &mut DemoUIModel, model: &mut DemoUIModel) {
) {
if !self.background_panel_visible { if !self.background_panel_visible {
return; return;
} }
@ -445,44 +464,41 @@ where
let panel_position = vec2i(panel_x, panel_y); let panel_position = vec2i(panel_x, panel_y);
debug_ui_presenter.ui_presenter.draw_solid_rounded_rect( debug_ui_presenter.ui_presenter.draw_solid_rounded_rect(
device, device,
allocator,
RectI::new(panel_position, vec2i(BACKGROUND_PANEL_WIDTH, BACKGROUND_PANEL_HEIGHT)), RectI::new(panel_position, vec2i(BACKGROUND_PANEL_WIDTH, BACKGROUND_PANEL_HEIGHT)),
WINDOW_COLOR, WINDOW_COLOR,
); );
self.draw_background_menu_item( self.draw_background_menu_item(device,
device, allocator,
debug_ui_presenter, debug_ui_presenter,
BackgroundColor::Light, BackgroundColor::Light,
panel_position, panel_position,
action, action,
model, model);
); self.draw_background_menu_item(device,
self.draw_background_menu_item( allocator,
device, debug_ui_presenter,
debug_ui_presenter, BackgroundColor::Dark,
BackgroundColor::Dark, panel_position,
panel_position, action,
action, model);
model, self.draw_background_menu_item(device,
); allocator,
self.draw_background_menu_item( debug_ui_presenter,
device, BackgroundColor::Transparent,
debug_ui_presenter, panel_position,
BackgroundColor::Transparent, action,
panel_position, model);
action,
model,
);
} }
fn draw_rotate_panel( fn draw_rotate_panel(&mut self,
&mut self, device: &D,
device: &D, allocator: &mut GPUMemoryAllocator<D>,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
rotate_panel_x: i32, rotate_panel_x: i32,
action: &mut UIAction, action: &mut UIAction,
model: &mut DemoUIModel model: &mut DemoUIModel) {
) {
if !self.rotate_panel_visible { if !self.rotate_panel_visible {
return; return;
} }
@ -493,9 +509,9 @@ where
let rotate_panel_size = vec2i(ROTATE_PANEL_WIDTH, ROTATE_PANEL_HEIGHT); let rotate_panel_size = vec2i(ROTATE_PANEL_WIDTH, ROTATE_PANEL_HEIGHT);
debug_ui_presenter.ui_presenter.draw_solid_rounded_rect( debug_ui_presenter.ui_presenter.draw_solid_rounded_rect(
device, device,
allocator,
RectI::new(rotate_panel_origin, rotate_panel_size), RectI::new(rotate_panel_origin, rotate_panel_size),
WINDOW_COLOR, WINDOW_COLOR);
);
let (widget_x, widget_y) = (rotate_panel_x + PADDING, rotate_panel_y + PADDING); let (widget_x, widget_y) = (rotate_panel_x + PADDING, rotate_panel_y + PADDING);
let widget_rect = RectI::new(vec2i(widget_x, widget_y), let widget_rect = RectI::new(vec2i(widget_x, widget_y),
@ -503,8 +519,7 @@ where
if let Some(position) = debug_ui_presenter if let Some(position) = debug_ui_presenter
.ui_presenter .ui_presenter
.event_queue .event_queue
.handle_mouse_down_or_dragged_in_rect(widget_rect) .handle_mouse_down_or_dragged_in_rect(widget_rect) {
{
model.rotation = position.x(); model.rotation = position.x();
*action = UIAction::Rotate(model.rotation()); *action = UIAction::Rotate(model.rotation());
} }
@ -513,23 +528,25 @@ where
rotate_panel_y + PADDING + SLIDER_KNOB_HEIGHT / 2 - SLIDER_TRACK_HEIGHT / 2; rotate_panel_y + PADDING + SLIDER_KNOB_HEIGHT / 2 - SLIDER_TRACK_HEIGHT / 2;
let slider_track_rect = RectI::new(vec2i(widget_x, slider_track_y), let slider_track_rect = RectI::new(vec2i(widget_x, slider_track_y),
vec2i(SLIDER_WIDTH, SLIDER_TRACK_HEIGHT)); vec2i(SLIDER_WIDTH, SLIDER_TRACK_HEIGHT));
debug_ui_presenter.ui_presenter.draw_rect_outline(device, slider_track_rect, TEXT_COLOR); debug_ui_presenter.ui_presenter
.draw_rect_outline(device, allocator, slider_track_rect, TEXT_COLOR);
let slider_knob_x = widget_x + model.rotation - SLIDER_KNOB_WIDTH / 2; let slider_knob_x = widget_x + model.rotation - SLIDER_KNOB_WIDTH / 2;
let slider_knob_rect = RectI::new(vec2i(slider_knob_x, widget_y), let slider_knob_rect = RectI::new(vec2i(slider_knob_x, widget_y),
vec2i(SLIDER_KNOB_WIDTH, SLIDER_KNOB_HEIGHT)); vec2i(SLIDER_KNOB_WIDTH, SLIDER_KNOB_HEIGHT));
debug_ui_presenter.ui_presenter.draw_solid_rect(device, slider_knob_rect, TEXT_COLOR); debug_ui_presenter.ui_presenter
.draw_solid_rect(device, allocator, slider_knob_rect, TEXT_COLOR);
} }
fn draw_screenshot_menu_item<W>( fn draw_screenshot_menu_item<W>(&mut self,
&mut self, device: &D,
device: &D, allocator: &mut GPUMemoryAllocator<D>,
window: &mut W, window: &mut W,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
screenshot_type: ScreenshotType, screenshot_type: ScreenshotType,
panel_position: Vector2I, panel_position: Vector2I,
action: &mut UIAction, action: &mut UIAction)
) where W: Window { where W: Window {
let index = screenshot_type as i32; let index = screenshot_type as i32;
let text = format!("Save as {}...", screenshot_type.as_str()); let text = format!("Save as {}...", screenshot_type.as_str());
@ -538,6 +555,7 @@ where
let widget_rect = RectI::new(widget_origin, widget_size); let widget_rect = RectI::new(widget_origin, widget_size);
if self.draw_menu_item(device, if self.draw_menu_item(device,
allocator,
debug_ui_presenter, debug_ui_presenter,
&text, &text,
widget_rect, widget_rect,
@ -551,15 +569,14 @@ where
} }
} }
fn draw_background_menu_item( fn draw_background_menu_item(&mut self,
&mut self, device: &D,
device: &D, allocator: &mut GPUMemoryAllocator<D>,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
color: BackgroundColor, color: BackgroundColor,
panel_position: Vector2I, panel_position: Vector2I,
action: &mut UIAction, action: &mut UIAction,
model: &mut DemoUIModel, model: &mut DemoUIModel) {
) {
let (text, index) = (color.as_str(), color as i32); let (text, index) = (color.as_str(), color as i32);
let widget_size = vec2i(BACKGROUND_PANEL_WIDTH, BUTTON_HEIGHT); let widget_size = vec2i(BACKGROUND_PANEL_WIDTH, BUTTON_HEIGHT);
@ -568,6 +585,7 @@ where
let selected = color == model.background_color; let selected = color == model.background_color;
if self.draw_menu_item(device, if self.draw_menu_item(device,
allocator,
debug_ui_presenter, debug_ui_presenter,
text, text,
widget_rect, widget_rect,
@ -579,20 +597,21 @@ where
fn draw_menu_item(&self, fn draw_menu_item(&self,
device: &D, device: &D,
allocator: &mut GPUMemoryAllocator<D>,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
text: &str, text: &str,
widget_rect: RectI, widget_rect: RectI,
selected: bool) selected: bool)
-> bool { -> bool {
if selected { if selected {
debug_ui_presenter.ui_presenter.draw_solid_rounded_rect(device, debug_ui_presenter.ui_presenter
widget_rect, .draw_solid_rounded_rect(device, allocator, widget_rect, TEXT_COLOR);
TEXT_COLOR);
} }
let (text_x, text_y) = (PADDING * 2, BUTTON_TEXT_OFFSET); let (text_x, text_y) = (PADDING * 2, BUTTON_TEXT_OFFSET);
let text_position = widget_rect.origin() + vec2i(text_x, text_y); let text_position = widget_rect.origin() + vec2i(text_x, text_y);
debug_ui_presenter.ui_presenter.draw_text(device, text, text_position, selected); debug_ui_presenter.ui_presenter
.draw_text(device, allocator, text, text_position, selected);
debug_ui_presenter.ui_presenter debug_ui_presenter.ui_presenter
.event_queue .event_queue
@ -600,28 +619,31 @@ where
.is_some() .is_some()
} }
fn draw_effects_switch( fn draw_effects_switch(&self,
&self, device: &D,
device: &D, allocator: &mut GPUMemoryAllocator<D>,
action: &mut UIAction, action: &mut UIAction,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
text: &str, text: &str,
index: i32, index: i32,
window_y: i32, window_y: i32,
value: &mut bool) { value: &mut bool) {
let text_x = PADDING * 2; let text_x = PADDING * 2;
let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index; let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index;
debug_ui_presenter.ui_presenter.draw_text(device, text, vec2i(text_x, text_y), false); debug_ui_presenter.ui_presenter
.draw_text(device, allocator, text, vec2i(text_x, text_y), false);
let switch_width = debug_ui_presenter.ui_presenter.measure_segmented_control(2); let switch_width = debug_ui_presenter.ui_presenter.measure_segmented_control(2);
let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (switch_width + PADDING); let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (switch_width + PADDING);
let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index; let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index;
let switch_position = vec2i(switch_x, switch_y); let switch_position = vec2i(switch_x, switch_y);
let new_value = let new_value = debug_ui_presenter.ui_presenter
debug_ui_presenter .draw_text_switch(device,
.ui_presenter allocator,
.draw_text_switch(device, switch_position, &["Off", "On"], *value as u8) != 0; switch_position,
&["Off", "On"],
*value as u8) != 0;
if new_value != *value { if new_value != *value {
*action = UIAction::EffectsChanged; *action = UIAction::EffectsChanged;