Add a custom 3D demo using glow to egui_demo_app (#1546)
This commit is contained in:
parent
bb421c7e8a
commit
3a83a600bb
7 changed files with 287 additions and 72 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1133,6 +1133,7 @@ dependencies = [
|
||||||
"egui",
|
"egui",
|
||||||
"egui_demo_lib",
|
"egui_demo_lib",
|
||||||
"egui_extras",
|
"egui_extras",
|
||||||
|
"egui_glow",
|
||||||
"ehttp",
|
"ehttp",
|
||||||
"image",
|
"image",
|
||||||
"poll-promise",
|
"poll-promise",
|
||||||
|
|
|
@ -38,6 +38,7 @@ chrono = { version = "0.4", features = ["js-sys", "wasmbind"] }
|
||||||
eframe = { version = "0.17.0", path = "../eframe" }
|
eframe = { version = "0.17.0", path = "../eframe" }
|
||||||
egui = { version = "0.17.0", path = "../egui", features = ["extra_debug_asserts"] }
|
egui = { version = "0.17.0", path = "../egui", features = ["extra_debug_asserts"] }
|
||||||
egui_demo_lib = { version = "0.17.0", path = "../egui_demo_lib", features = ["chrono"] }
|
egui_demo_lib = { version = "0.17.0", path = "../egui_demo_lib", features = ["chrono"] }
|
||||||
|
egui_glow = { version = "0.17.0", path = "../egui_glow" }
|
||||||
|
|
||||||
# Optional dependencies:
|
# Optional dependencies:
|
||||||
|
|
||||||
|
|
183
egui_demo_app/src/apps/custom3d.rs
Normal file
183
egui_demo_app/src/apps/custom3d.rs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use egui::mutex::Mutex;
|
||||||
|
use egui_glow::glow;
|
||||||
|
|
||||||
|
pub struct Custom3d {
|
||||||
|
/// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
|
||||||
|
rotating_triangle: Arc<Mutex<RotatingTriangle>>,
|
||||||
|
angle: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Custom3d {
|
||||||
|
pub fn new(gl: &glow::Context) -> Self {
|
||||||
|
Self {
|
||||||
|
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(gl))),
|
||||||
|
angle: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for Custom3d {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
ui.label("The triangle is being painted using ");
|
||||||
|
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
|
||||||
|
ui.label(" (OpenGL).");
|
||||||
|
});
|
||||||
|
ui.label(
|
||||||
|
"It's not a very impressive demo, but it shows you can embed 3D inside of egui.",
|
||||||
|
);
|
||||||
|
|
||||||
|
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||||
|
self.custom_painting(ui);
|
||||||
|
});
|
||||||
|
ui.label("Drag to rotate!");
|
||||||
|
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_exit(&mut self, gl: &glow::Context) {
|
||||||
|
self.rotating_triangle.lock().destroy(gl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Custom3d {
|
||||||
|
fn custom_painting(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let (rect, response) =
|
||||||
|
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
|
||||||
|
|
||||||
|
self.angle += response.drag_delta().x * 0.01;
|
||||||
|
|
||||||
|
// Clone locals so we can move them into the paint callback:
|
||||||
|
let angle = self.angle;
|
||||||
|
let rotating_triangle = self.rotating_triangle.clone();
|
||||||
|
|
||||||
|
let callback = egui::PaintCallback {
|
||||||
|
rect,
|
||||||
|
callback: std::sync::Arc::new(move |_info, render_ctx| {
|
||||||
|
if let Some(painter) = render_ctx.downcast_ref::<egui_glow::Painter>() {
|
||||||
|
rotating_triangle.lock().paint(painter.gl(), angle);
|
||||||
|
} else {
|
||||||
|
eprintln!("Can't do custom painting because we are not using a glow context");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
ui.painter().add(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RotatingTriangle {
|
||||||
|
program: glow::Program,
|
||||||
|
vertex_array: glow::VertexArray,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unsafe_code)] // we need unsafe code to use glow
|
||||||
|
impl RotatingTriangle {
|
||||||
|
fn new(gl: &glow::Context) -> Self {
|
||||||
|
use glow::HasContext as _;
|
||||||
|
|
||||||
|
let shader_version = if cfg!(target_arch = "wasm32") {
|
||||||
|
"#version 300 es"
|
||||||
|
} else {
|
||||||
|
"#version 410"
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let program = gl.create_program().expect("Cannot create program");
|
||||||
|
|
||||||
|
let (vertex_shader_source, fragment_shader_source) = (
|
||||||
|
r#"
|
||||||
|
const vec2 verts[3] = vec2[3](
|
||||||
|
vec2(0.0, 1.0),
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2(1.0, -1.0)
|
||||||
|
);
|
||||||
|
const vec4 colors[3] = vec4[3](
|
||||||
|
vec4(1.0, 0.0, 0.0, 1.0),
|
||||||
|
vec4(0.0, 1.0, 0.0, 1.0),
|
||||||
|
vec4(0.0, 0.0, 1.0, 1.0)
|
||||||
|
);
|
||||||
|
out vec4 v_color;
|
||||||
|
uniform float u_angle;
|
||||||
|
void main() {
|
||||||
|
v_color = colors[gl_VertexID];
|
||||||
|
gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0);
|
||||||
|
gl_Position.x *= cos(u_angle);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
precision mediump float;
|
||||||
|
in vec4 v_color;
|
||||||
|
out vec4 out_color;
|
||||||
|
void main() {
|
||||||
|
out_color = v_color;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let shader_sources = [
|
||||||
|
(glow::VERTEX_SHADER, vertex_shader_source),
|
||||||
|
(glow::FRAGMENT_SHADER, fragment_shader_source),
|
||||||
|
];
|
||||||
|
|
||||||
|
let shaders: Vec<_> = shader_sources
|
||||||
|
.iter()
|
||||||
|
.map(|(shader_type, shader_source)| {
|
||||||
|
let shader = gl
|
||||||
|
.create_shader(*shader_type)
|
||||||
|
.expect("Cannot create shader");
|
||||||
|
gl.shader_source(shader, &format!("{}\n{}", shader_version, shader_source));
|
||||||
|
gl.compile_shader(shader);
|
||||||
|
if !gl.get_shader_compile_status(shader) {
|
||||||
|
panic!("{}", gl.get_shader_info_log(shader));
|
||||||
|
}
|
||||||
|
gl.attach_shader(program, shader);
|
||||||
|
shader
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
gl.link_program(program);
|
||||||
|
if !gl.get_program_link_status(program) {
|
||||||
|
panic!("{}", gl.get_program_info_log(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
for shader in shaders {
|
||||||
|
gl.detach_shader(program, shader);
|
||||||
|
gl.delete_shader(shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertex_array = gl
|
||||||
|
.create_vertex_array()
|
||||||
|
.expect("Cannot create vertex array");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
program,
|
||||||
|
vertex_array,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self, gl: &glow::Context) {
|
||||||
|
use glow::HasContext as _;
|
||||||
|
unsafe {
|
||||||
|
gl.delete_program(self.program);
|
||||||
|
gl.delete_vertex_array(self.vertex_array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&self, gl: &glow::Context, angle: f32) {
|
||||||
|
use glow::HasContext as _;
|
||||||
|
unsafe {
|
||||||
|
gl.use_program(Some(self.program));
|
||||||
|
gl.uniform_1_f32(
|
||||||
|
gl.get_uniform_location(self.program, "u_angle").as_ref(),
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
gl.bind_vertex_array(Some(self.vertex_array));
|
||||||
|
gl.draw_arrays(glow::TRIANGLES, 0, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
|
mod custom3d;
|
||||||
mod fractal_clock;
|
mod fractal_clock;
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
mod http_app;
|
mod http_app;
|
||||||
|
|
||||||
|
pub use custom3d::Custom3d;
|
||||||
pub use fractal_clock::FractalClock;
|
pub use fractal_clock::FractalClock;
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
pub use http_app::HttpApp;
|
pub use http_app::HttpApp;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use egui_glow::glow;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
struct EasyMarkApp {
|
struct EasyMarkApp {
|
||||||
|
@ -41,6 +43,7 @@ impl eframe::App for FractalClockApp {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -67,71 +70,90 @@ impl eframe::App for ColorTestApp {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// All the different demo apps.
|
/// The state that we persist (serialize).
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
pub struct Apps {
|
pub struct State {
|
||||||
demo: DemoApp,
|
demo: DemoApp,
|
||||||
easy_mark_editor: EasyMarkApp,
|
easy_mark_editor: EasyMarkApp,
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
http: crate::apps::HttpApp,
|
http: crate::apps::HttpApp,
|
||||||
clock: FractalClockApp,
|
clock: FractalClockApp,
|
||||||
color_test: ColorTestApp,
|
color_test: ColorTestApp,
|
||||||
|
|
||||||
|
selected_anchor: String,
|
||||||
|
backend_panel: super::backend_panel::BackendPanel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Apps {
|
/// Wraps many demo/test apps into one.
|
||||||
fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &str, &mut dyn eframe::App)> {
|
pub struct WrapApp {
|
||||||
|
state: State,
|
||||||
|
// not serialized (because it contains OpenGL buffers etc)
|
||||||
|
custom3d: crate::apps::Custom3d,
|
||||||
|
dropped_files: Vec<egui::DroppedFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrapApp {
|
||||||
|
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
|
let mut slf = Self {
|
||||||
|
state: State::default(),
|
||||||
|
custom3d: crate::apps::Custom3d::new(&cc.gl),
|
||||||
|
dropped_files: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "persistence")]
|
||||||
|
if let Some(storage) = cc.storage {
|
||||||
|
if let Some(state) = eframe::get_value(storage, eframe::APP_KEY) {
|
||||||
|
slf.state = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apps_iter_mut(&mut self) -> impl Iterator<Item = (&str, &str, &mut dyn eframe::App)> {
|
||||||
vec![
|
vec![
|
||||||
("✨ Demos", "demo", &mut self.demo as &mut dyn eframe::App),
|
(
|
||||||
|
"✨ Demos",
|
||||||
|
"demo",
|
||||||
|
&mut self.state.demo as &mut dyn eframe::App,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"🖹 EasyMark editor",
|
"🖹 EasyMark editor",
|
||||||
"easymark",
|
"easymark",
|
||||||
&mut self.easy_mark_editor as &mut dyn eframe::App,
|
&mut self.state.easy_mark_editor as &mut dyn eframe::App,
|
||||||
),
|
),
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
("⬇ HTTP", "http", &mut self.http as &mut dyn eframe::App),
|
(
|
||||||
|
"⬇ HTTP",
|
||||||
|
"http",
|
||||||
|
&mut self.state.http as &mut dyn eframe::App,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"🕑 Fractal Clock",
|
"🕑 Fractal Clock",
|
||||||
"clock",
|
"clock",
|
||||||
&mut self.clock as &mut dyn eframe::App,
|
&mut self.state.clock as &mut dyn eframe::App,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"🔺 3D painting",
|
||||||
|
"custom3e",
|
||||||
|
&mut self.custom3d as &mut dyn eframe::App,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"🎨 Color test",
|
"🎨 Color test",
|
||||||
"colors",
|
"colors",
|
||||||
&mut self.color_test as &mut dyn eframe::App,
|
&mut self.state.color_test as &mut dyn eframe::App,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps many demo/test apps into one.
|
|
||||||
#[derive(Default)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
|
||||||
pub struct WrapApp {
|
|
||||||
selected_anchor: String,
|
|
||||||
apps: Apps,
|
|
||||||
backend_panel: super::backend_panel::BackendPanel,
|
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
|
||||||
dropped_files: Vec<egui::DroppedFile>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WrapApp {
|
|
||||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
|
||||||
#[cfg(feature = "persistence")]
|
|
||||||
if let Some(storage) = _cc.storage {
|
|
||||||
return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default();
|
|
||||||
}
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl eframe::App for WrapApp {
|
impl eframe::App for WrapApp {
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||||
eframe::set_value(storage, eframe::APP_KEY, self);
|
eframe::set_value(storage, eframe::APP_KEY, &self.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_color(&self) -> egui::Rgba {
|
fn clear_color(&self) -> egui::Rgba {
|
||||||
|
@ -141,12 +163,13 @@ impl eframe::App for WrapApp {
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
if let Some(web_info) = frame.info().web_info.as_ref() {
|
if let Some(web_info) = frame.info().web_info.as_ref() {
|
||||||
if let Some(anchor) = web_info.location.hash.strip_prefix('#') {
|
if let Some(anchor) = web_info.location.hash.strip_prefix('#') {
|
||||||
self.selected_anchor = anchor.to_owned();
|
self.state.selected_anchor = anchor.to_owned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.selected_anchor.is_empty() {
|
if self.state.selected_anchor.is_empty() {
|
||||||
self.selected_anchor = self.apps.iter_mut().next().unwrap().0.to_owned();
|
let selected_anchor = self.apps_iter_mut().next().unwrap().0.to_owned();
|
||||||
|
self.state.selected_anchor = selected_anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
egui::TopBottomPanel::top("wrap_app_top_bar").show(ctx, |ui| {
|
egui::TopBottomPanel::top("wrap_app_top_bar").show(ctx, |ui| {
|
||||||
|
@ -154,11 +177,11 @@ impl eframe::App for WrapApp {
|
||||||
self.bar_contents(ui, frame);
|
self.bar_contents(ui, frame);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.backend_panel.update(ctx, frame);
|
self.state.backend_panel.update(ctx, frame);
|
||||||
|
|
||||||
if self.backend_panel.open || ctx.memory().everything_is_visible() {
|
if self.state.backend_panel.open || ctx.memory().everything_is_visible() {
|
||||||
egui::SidePanel::left("backend_panel").show(ctx, |ui| {
|
egui::SidePanel::left("backend_panel").show(ctx, |ui| {
|
||||||
self.backend_panel.ui(ui, frame);
|
self.state.backend_panel.ui(ui, frame);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
@ -172,7 +195,7 @@ impl eframe::App for WrapApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.button("Reset everything").clicked() {
|
if ui.button("Reset everything").clicked() {
|
||||||
*self = Default::default();
|
self.state = Default::default();
|
||||||
*ui.ctx().memory() = Default::default();
|
*ui.ctx().memory() = Default::default();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -181,21 +204,26 @@ impl eframe::App for WrapApp {
|
||||||
|
|
||||||
let mut found_anchor = false;
|
let mut found_anchor = false;
|
||||||
|
|
||||||
for (_name, anchor, app) in self.apps.iter_mut() {
|
let selected_anchor = self.state.selected_anchor.clone();
|
||||||
if anchor == self.selected_anchor || ctx.memory().everything_is_visible() {
|
for (_name, anchor, app) in self.apps_iter_mut() {
|
||||||
|
if anchor == selected_anchor || ctx.memory().everything_is_visible() {
|
||||||
app.update(ctx, frame);
|
app.update(ctx, frame);
|
||||||
found_anchor = true;
|
found_anchor = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found_anchor {
|
if !found_anchor {
|
||||||
self.selected_anchor = "demo".into();
|
self.state.selected_anchor = "demo".into();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backend_panel.end_of_frame(ctx);
|
self.state.backend_panel.end_of_frame(ctx);
|
||||||
|
|
||||||
self.ui_file_drag_and_drop(ctx);
|
self.ui_file_drag_and_drop(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_exit(&mut self, gl: &glow::Context) {
|
||||||
|
self.custom3d.on_exit(gl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WrapApp {
|
impl WrapApp {
|
||||||
|
@ -205,27 +233,29 @@ impl WrapApp {
|
||||||
ui.horizontal_wrapped(|ui| {
|
ui.horizontal_wrapped(|ui| {
|
||||||
egui::widgets::global_dark_light_mode_switch(ui);
|
egui::widgets::global_dark_light_mode_switch(ui);
|
||||||
|
|
||||||
ui.checkbox(&mut self.backend_panel.open, "💻 Backend");
|
ui.checkbox(&mut self.state.backend_panel.open, "💻 Backend");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
for (name, anchor, _app) in self.apps.iter_mut() {
|
let mut selected_anchor = self.state.selected_anchor.clone();
|
||||||
|
for (name, anchor, _app) in self.apps_iter_mut() {
|
||||||
if ui
|
if ui
|
||||||
.selectable_label(self.selected_anchor == anchor, name)
|
.selectable_label(selected_anchor == anchor, name)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
self.selected_anchor = anchor.to_owned();
|
selected_anchor = anchor.to_owned();
|
||||||
if frame.is_web() {
|
if frame.is_web() {
|
||||||
ui.output().open_url(format!("#{}", anchor));
|
ui.output().open_url(format!("#{}", anchor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.state.selected_anchor = selected_anchor;
|
||||||
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(), |ui| {
|
ui.with_layout(egui::Layout::right_to_left(), |ui| {
|
||||||
if false {
|
if false {
|
||||||
// TODO: fix the overlap on small screens
|
// TODO: fix the overlap on small screens
|
||||||
if let Some(seconds_since_midnight) = crate::seconds_since_midnight() {
|
if let Some(seconds_since_midnight) = crate::seconds_since_midnight() {
|
||||||
if clock_button(ui, seconds_since_midnight).clicked() {
|
if clock_button(ui, seconds_since_midnight).clicked() {
|
||||||
self.selected_anchor = "clock".to_owned();
|
self.state.selected_anchor = "clock".to_owned();
|
||||||
if frame.is_web() {
|
if frame.is_web() {
|
||||||
ui.output().open_url("#clock");
|
ui.output().open_url("#clock");
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ impl super::Demo for WidgetGallery {
|
||||||
egui::Window::new(self.name())
|
egui::Window::new(self.name())
|
||||||
.open(open)
|
.open(open)
|
||||||
.resizable(true)
|
.resizable(true)
|
||||||
.default_width(300.0)
|
.default_width(280.0)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
use super::View as _;
|
use super::View as _;
|
||||||
self.ui(ui);
|
self.ui(ui);
|
||||||
|
|
|
@ -20,6 +20,27 @@ pub use glow::Context;
|
||||||
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
|
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
|
||||||
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
|
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum TextureFilter {
|
||||||
|
Linear,
|
||||||
|
Nearest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TextureFilter {
|
||||||
|
fn default() -> Self {
|
||||||
|
TextureFilter::Linear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureFilter {
|
||||||
|
pub(crate) fn glow_code(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
TextureFilter::Linear => glow::LINEAR,
|
||||||
|
TextureFilter::Nearest => glow::NEAREST,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An OpenGL painter using [`glow`].
|
/// An OpenGL painter using [`glow`].
|
||||||
///
|
///
|
||||||
/// This is responsible for painting egui and managing egui textures.
|
/// This is responsible for painting egui and managing egui textures.
|
||||||
|
@ -56,27 +77,6 @@ pub struct Painter {
|
||||||
destroyed: bool,
|
destroyed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub enum TextureFilter {
|
|
||||||
Linear,
|
|
||||||
Nearest,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TextureFilter {
|
|
||||||
fn default() -> Self {
|
|
||||||
TextureFilter::Linear
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextureFilter {
|
|
||||||
pub(crate) fn glow_code(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
TextureFilter::Linear => glow::LINEAR,
|
|
||||||
TextureFilter::Nearest => glow::NEAREST,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
/// Create painter.
|
/// Create painter.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in a new issue