├── .github └── FUNDING.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── font.ttf ├── icon.png └── sound.ogg ├── build.rs ├── conf.ini ├── crates ├── ecolor │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── cint_impl.rs │ │ ├── color32.rs │ │ ├── hex_color_macro.rs │ │ ├── hsva.rs │ │ ├── hsva_gamma.rs │ │ ├── lib.rs │ │ └── rgba.rs ├── eframe │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── epi.rs │ │ ├── lib.rs │ │ ├── native │ │ ├── epi_integration.rs │ │ ├── file_storage.rs │ │ ├── mod.rs │ │ └── run.rs │ │ └── web │ │ ├── backend.rs │ │ ├── events.rs │ │ ├── input.rs │ │ ├── mod.rs │ │ ├── screen_reader.rs │ │ ├── storage.rs │ │ ├── text_agent.rs │ │ ├── web_painter.rs │ │ ├── web_painter_glow.rs │ │ └── web_painter_wgpu.rs ├── egui-wgpu │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── egui.wgsl │ │ ├── lib.rs │ │ ├── renderer.rs │ │ └── winit.rs ├── egui-winit │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── clipboard.rs │ │ ├── lib.rs │ │ └── window_settings.rs ├── egui │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ └── README.md │ └── src │ │ ├── animation_manager.rs │ │ ├── containers │ │ ├── area.rs │ │ ├── collapsing_header.rs │ │ ├── combo_box.rs │ │ ├── frame.rs │ │ ├── mod.rs │ │ ├── panel.rs │ │ ├── popup.rs │ │ ├── resize.rs │ │ ├── scroll_area.rs │ │ └── window.rs │ │ ├── context.rs │ │ ├── data │ │ ├── input.rs │ │ ├── mod.rs │ │ └── output.rs │ │ ├── frame_state.rs │ │ ├── grid.rs │ │ ├── gui_zoom.rs │ │ ├── id.rs │ │ ├── input_state.rs │ │ ├── input_state │ │ └── touch_state.rs │ │ ├── introspection.rs │ │ ├── layers.rs │ │ ├── layout.rs │ │ ├── lib.rs │ │ ├── memory.rs │ │ ├── menu.rs │ │ ├── os.rs │ │ ├── painter.rs │ │ ├── placer.rs │ │ ├── response.rs │ │ ├── sense.rs │ │ ├── style.rs │ │ ├── ui.rs │ │ ├── util │ │ ├── cache.rs │ │ ├── fixed_cache.rs │ │ ├── id_type_map.rs │ │ ├── mod.rs │ │ └── undoer.rs │ │ ├── widget_text.rs │ │ └── widgets │ │ ├── button.rs │ │ ├── color_picker.rs │ │ ├── drag_value.rs │ │ ├── hyperlink.rs │ │ ├── image.rs │ │ ├── label.rs │ │ ├── mod.rs │ │ ├── plot │ │ ├── items │ │ │ ├── bar.rs │ │ │ ├── box_elem.rs │ │ │ ├── mod.rs │ │ │ ├── rect_elem.rs │ │ │ └── values.rs │ │ ├── legend.rs │ │ ├── mod.rs │ │ └── transform.rs │ │ ├── progress_bar.rs │ │ ├── selected_label.rs │ │ ├── separator.rs │ │ ├── slider.rs │ │ ├── spinner.rs │ │ └── text_edit │ │ ├── builder.rs │ │ ├── cursor_range.rs │ │ ├── mod.rs │ │ ├── output.rs │ │ ├── state.rs │ │ └── text_buffer.rs ├── egui_demo_app │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── apps │ │ ├── custom3d_glow.rs │ │ ├── custom3d_wgpu.rs │ │ ├── custom3d_wgpu_shader.wgsl │ │ ├── fractal_clock.rs │ │ ├── http_app.rs │ │ └── mod.rs │ │ ├── backend_panel.rs │ │ ├── frame_history.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ └── wrap_app.rs ├── egui_demo_lib │ ├── Cargo.toml │ ├── README.md │ ├── benches │ │ └── benchmark.rs │ └── src │ │ ├── color_test.rs │ │ ├── demo │ │ ├── about.rs │ │ ├── code_editor.rs │ │ ├── code_example.rs │ │ ├── context_menu.rs │ │ ├── dancing_strings.rs │ │ ├── demo_app_windows.rs │ │ ├── drag_and_drop.rs │ │ ├── font_book.rs │ │ ├── highlighting.rs │ │ ├── layout_test.rs │ │ ├── misc_demo_window.rs │ │ ├── mod.rs │ │ ├── multi_touch.rs │ │ ├── paint_bezier.rs │ │ ├── painting.rs │ │ ├── password.rs │ │ ├── plot_demo.rs │ │ ├── scrolling.rs │ │ ├── sliders.rs │ │ ├── strip_demo.rs │ │ ├── table_demo.rs │ │ ├── tests.rs │ │ ├── text_edit.rs │ │ ├── toggle_switch.rs │ │ ├── widget_gallery.rs │ │ ├── window_options.rs │ │ └── window_with_panels.rs │ │ ├── easy_mark │ │ ├── easy_mark_editor.rs │ │ ├── easy_mark_highlighter.rs │ │ ├── easy_mark_parser.rs │ │ ├── easy_mark_viewer.rs │ │ └── mod.rs │ │ ├── lib.rs │ │ └── syntax_highlighting.rs ├── egui_extras │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── datepicker │ │ ├── button.rs │ │ ├── mod.rs │ │ └── popup.rs │ │ ├── image.rs │ │ ├── layout.rs │ │ ├── lib.rs │ │ ├── sizing.rs │ │ ├── strip.rs │ │ └── table.rs ├── egui_glium │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ ├── native_texture.rs │ │ └── pure_glium.rs │ └── src │ │ ├── lib.rs │ │ ├── painter.rs │ │ └── shader │ │ ├── fragment_100es.glsl │ │ ├── fragment_120.glsl │ │ ├── fragment_140.glsl │ │ ├── fragment_300es.glsl │ │ ├── vertex_100es.glsl │ │ ├── vertex_120.glsl │ │ ├── vertex_140.glsl │ │ └── vertex_300es.glsl ├── egui_glow │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ └── pure_glow.rs │ └── src │ │ ├── lib.rs │ │ ├── misc_util.rs │ │ ├── painter.rs │ │ ├── shader │ │ ├── fragment.glsl │ │ └── vertex.glsl │ │ ├── shader_version.rs │ │ ├── vao.rs │ │ └── winit.rs ├── egui_web │ ├── CHANGELOG.md │ └── README.md ├── emath │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── align.rs │ │ ├── history.rs │ │ ├── lib.rs │ │ ├── numeric.rs │ │ ├── pos2.rs │ │ ├── rect.rs │ │ ├── rect_transform.rs │ │ ├── rot2.rs │ │ ├── smart_aim.rs │ │ └── vec2.rs └── epaint │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── benches │ └── benchmark.rs │ ├── fonts │ ├── Hack-Regular.ttf │ ├── Hack-Regular.txt │ ├── NotoEmoji-Regular.ttf │ ├── OFL.txt │ ├── UFL.txt │ ├── Ubuntu-Light.ttf │ ├── emoji-icon-font-mit-license.txt │ ├── emoji-icon-font.ttf │ └── list_fonts.py │ └── src │ ├── bezier.rs │ ├── image.rs │ ├── lib.rs │ ├── mesh.rs │ ├── mutex.rs │ ├── shadow.rs │ ├── shape.rs │ ├── shape_transform.rs │ ├── stats.rs │ ├── stroke.rs │ ├── tessellator.rs │ ├── text │ ├── cursor.rs │ ├── font.rs │ ├── fonts.rs │ ├── mod.rs │ ├── text_layout.rs │ └── text_layout_types.rs │ ├── texture_atlas.rs │ ├── texture_handle.rs │ ├── textures.rs │ └── util │ ├── mod.rs │ └── ordered_float.rs ├── icon.ico ├── pic.gif └── src ├── lib.rs └── main.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | ko_fi: hoothin 3 | custom: ["https://paypal.me/hoothin", "https://afdian.net/a/hoothin", "https://smms.app/image/lEqKWLHG7UBO6AY"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | *.jpeg 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "crates/ecolor", 5 | "crates/egui_extras", 6 | "crates/egui_glow", 7 | "crates/egui-wgpu", 8 | "crates/egui-winit", 9 | "crates/egui", 10 | "crates/emath", 11 | "crates/epaint", 12 | ] 13 | 14 | [package] 15 | name = "rust_clock" 16 | authors = ["Hoothin "] 17 | license = "MIT" 18 | version = "0.2.0" 19 | edition = "2021" 20 | description = "Clock popup every half hour" 21 | build = "build.rs" 22 | 23 | [dependencies] 24 | egui_extras = { path = "crates/egui_extras", features = [ 25 | "image" 26 | ] } 27 | eframe = { path = "crates/eframe", features = [ 28 | "__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO 29 | ] } 30 | tray-icon = "0.5.1" 31 | image = "0.24" 32 | chrono = "0.4.24" 33 | rust-ini = "0.19.0" 34 | rodio = "0.17.1" 35 | 36 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 37 | 38 | 39 | # Optimize all dependencies even in debug builds (does not affect workspace packages): 40 | [profile.dev.package."*"] 41 | opt-level = "z" 42 | 43 | [patch.crates-io] 44 | winit = { git = "https://github.com/rust-windowing/winit", rev = "2e4338bb8dddf820c9bcda23d6b7a0d8a6208831" } 45 | 46 | [profile.release] 47 | lto = true 48 | strip = true 49 | codegen-units = 1 50 | panic = 'abort' 51 | 52 | [package.metadata.winres] 53 | OriginalFilename = "rust_clock.exe" 54 | LegalCopyright = "Copyright Hoothin © 2023" 55 | FileDescription = "Clock popup every half hour" 56 | 57 | [build-dependencies] 58 | winres = "0.1" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 hoothin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoothin/RustClock/a2dcc767b7d0aa2c6005136f22db3624eaaf367e/assets/font.ttf -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoothin/RustClock/a2dcc767b7d0aa2c6005136f22db3624eaaf367e/assets/icon.png -------------------------------------------------------------------------------- /assets/sound.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoothin/RustClock/a2dcc767b7d0aa2c6005136f22db3624eaaf367e/assets/sound.ogg -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate winres; 2 | 3 | fn main() { 4 | if cfg!(target_os = "windows") { 5 | let mut res = winres::WindowsResource::new(); 6 | res.set_icon("icon.ico"); 7 | res.compile().unwrap(); 8 | } 9 | } -------------------------------------------------------------------------------- /conf.ini: -------------------------------------------------------------------------------- 1 | [Config] 2 | time=:30:,:00: 3 | #sound=sound.ogg 4 | countdown=::30,::10 -------------------------------------------------------------------------------- /crates/ecolor/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for ecolor 2 | All notable changes to the `ecolor` crate will be noted in this file. 3 | 4 | 5 | ## Unreleased 6 | * Add `Color32::gamma_multiply` ([#2437](https://github.com/emilk/egui/pull/2437)). 7 | 8 | 9 | ## 0.20.0 - 2022-12-08 10 | * Split out `ecolor` crate from `epaint` 11 | -------------------------------------------------------------------------------- /crates/ecolor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ecolor" 3 | version = "0.20.0" 4 | authors = [ 5 | "Emil Ernerfeldt ", 6 | "Andreas Reich ", 7 | ] 8 | description = "Color structs and color conversion utilities" 9 | edition = "2021" 10 | rust-version = "1.65" 11 | homepage = "https://github.com/emilk/egui" 12 | license = "MIT OR Apache-2.0" 13 | readme = "README.md" 14 | repository = "https://github.com/emilk/egui" 15 | categories = ["mathematics", "encoding"] 16 | keywords = ["gui", "color", "conversion", "gamedev", "images"] 17 | include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] 18 | 19 | [package.metadata.docs.rs] 20 | all-features = true 21 | 22 | [lib] 23 | 24 | 25 | [features] 26 | default = [] 27 | 28 | ## Enable additional checks if debug assertions are enabled (debug builds). 29 | extra_debug_asserts = [] 30 | ## Always enable additional checks. 31 | extra_asserts = [] 32 | 33 | 34 | [dependencies] 35 | #! ### Optional dependencies 36 | 37 | ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `ecolor` types to `&[u8]`. 38 | bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } 39 | 40 | ## [`cint`](https://docs.rs/cint) enables interopability with other color libraries. 41 | cint = { version = "0.3.1", optional = true } 42 | 43 | ## Enable the [`hex_color`] macro. 44 | color-hex = { version = "0.2.0", optional = true } 45 | 46 | ## Enable this when generating docs. 47 | document-features = { version = "0.2", optional = true } 48 | 49 | ## Allow serialization using [`serde`](https://docs.rs/serde). 50 | serde = { version = "1", optional = true, features = ["derive"] } 51 | -------------------------------------------------------------------------------- /crates/ecolor/README.md: -------------------------------------------------------------------------------- 1 | # ecolor - egui color library 2 | 3 | A simple color storage and conversion library. 4 | 5 | Made for [`egui`](https://github.com/emilk/egui/). 6 | -------------------------------------------------------------------------------- /crates/ecolor/src/cint_impl.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use cint::{Alpha, ColorInterop, EncodedSrgb, Hsv, LinearSrgb, PremultipliedAlpha}; 3 | 4 | // ---- Color32 ---- 5 | 6 | impl From>> for Color32 { 7 | fn from(srgba: Alpha>) -> Self { 8 | let Alpha { 9 | color: EncodedSrgb { r, g, b }, 10 | alpha: a, 11 | } = srgba; 12 | 13 | Color32::from_rgba_unmultiplied(r, g, b, a) 14 | } 15 | } 16 | 17 | // No From for Alpha<_> because Color32 is premultiplied 18 | 19 | impl From>> for Color32 { 20 | fn from(srgba: PremultipliedAlpha>) -> Self { 21 | let PremultipliedAlpha { 22 | color: EncodedSrgb { r, g, b }, 23 | alpha: a, 24 | } = srgba; 25 | 26 | Color32::from_rgba_premultiplied(r, g, b, a) 27 | } 28 | } 29 | 30 | impl From for PremultipliedAlpha> { 31 | fn from(col: Color32) -> Self { 32 | let (r, g, b, a) = col.to_tuple(); 33 | 34 | PremultipliedAlpha { 35 | color: EncodedSrgb { r, g, b }, 36 | alpha: a, 37 | } 38 | } 39 | } 40 | 41 | impl From>> for Color32 { 42 | fn from(srgba: PremultipliedAlpha>) -> Self { 43 | let PremultipliedAlpha { 44 | color: EncodedSrgb { r, g, b }, 45 | alpha: a, 46 | } = srgba; 47 | 48 | // This is a bit of an abuse of the function name but it does what we want. 49 | let r = linear_u8_from_linear_f32(r); 50 | let g = linear_u8_from_linear_f32(g); 51 | let b = linear_u8_from_linear_f32(b); 52 | let a = linear_u8_from_linear_f32(a); 53 | 54 | Color32::from_rgba_premultiplied(r, g, b, a) 55 | } 56 | } 57 | 58 | impl From for PremultipliedAlpha> { 59 | fn from(col: Color32) -> Self { 60 | let (r, g, b, a) = col.to_tuple(); 61 | 62 | // This is a bit of an abuse of the function name but it does what we want. 63 | let r = linear_f32_from_linear_u8(r); 64 | let g = linear_f32_from_linear_u8(g); 65 | let b = linear_f32_from_linear_u8(b); 66 | let a = linear_f32_from_linear_u8(a); 67 | 68 | PremultipliedAlpha { 69 | color: EncodedSrgb { r, g, b }, 70 | alpha: a, 71 | } 72 | } 73 | } 74 | 75 | impl ColorInterop for Color32 { 76 | type CintTy = PremultipliedAlpha>; 77 | } 78 | 79 | // ---- Rgba ---- 80 | 81 | impl From>> for Rgba { 82 | fn from(srgba: PremultipliedAlpha>) -> Self { 83 | let PremultipliedAlpha { 84 | color: LinearSrgb { r, g, b }, 85 | alpha: a, 86 | } = srgba; 87 | 88 | Rgba([r, g, b, a]) 89 | } 90 | } 91 | 92 | impl From for PremultipliedAlpha> { 93 | fn from(col: Rgba) -> Self { 94 | let (r, g, b, a) = col.to_tuple(); 95 | 96 | PremultipliedAlpha { 97 | color: LinearSrgb { r, g, b }, 98 | alpha: a, 99 | } 100 | } 101 | } 102 | 103 | impl ColorInterop for Rgba { 104 | type CintTy = PremultipliedAlpha>; 105 | } 106 | 107 | // ---- Hsva ---- 108 | 109 | impl From>> for Hsva { 110 | fn from(srgba: Alpha>) -> Self { 111 | let Alpha { 112 | color: Hsv { h, s, v }, 113 | alpha: a, 114 | } = srgba; 115 | 116 | Hsva::new(h, s, v, a) 117 | } 118 | } 119 | 120 | impl From for Alpha> { 121 | fn from(col: Hsva) -> Self { 122 | let Hsva { h, s, v, a } = col; 123 | 124 | Alpha { 125 | color: Hsv { h, s, v }, 126 | alpha: a, 127 | } 128 | } 129 | } 130 | 131 | impl ColorInterop for Hsva { 132 | type CintTy = Alpha>; 133 | } 134 | 135 | // ---- HsvaGamma ---- 136 | 137 | impl ColorInterop for HsvaGamma { 138 | type CintTy = Alpha>; 139 | } 140 | 141 | impl From>> for HsvaGamma { 142 | fn from(srgba: Alpha>) -> Self { 143 | let Alpha { 144 | color: Hsv { h, s, v }, 145 | alpha: a, 146 | } = srgba; 147 | 148 | Hsva::new(h, s, v, a).into() 149 | } 150 | } 151 | 152 | impl From for Alpha> { 153 | fn from(col: HsvaGamma) -> Self { 154 | let Hsva { h, s, v, a } = col.into(); 155 | 156 | Alpha { 157 | color: Hsv { h, s, v }, 158 | alpha: a, 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /crates/ecolor/src/hex_color_macro.rs: -------------------------------------------------------------------------------- 1 | /// Construct a [`crate::Color32`] from a hex RGB or RGBA string. 2 | /// 3 | /// ``` 4 | /// # use ecolor::{hex_color, Color32}; 5 | /// assert_eq!(hex_color!("#202122"), Color32::from_rgb(0x20, 0x21, 0x22)); 6 | /// assert_eq!(hex_color!("#abcdef12"), Color32::from_rgba_unmultiplied(0xab, 0xcd, 0xef, 0x12)); 7 | /// ``` 8 | #[macro_export] 9 | macro_rules! hex_color { 10 | ($s:literal) => {{ 11 | let array = color_hex::color_from_hex!($s); 12 | if array.len() == 3 { 13 | $crate::Color32::from_rgb(array[0], array[1], array[2]) 14 | } else { 15 | #[allow(unconditional_panic)] 16 | $crate::Color32::from_rgba_unmultiplied(array[0], array[1], array[2], array[3]) 17 | } 18 | }}; 19 | } 20 | 21 | #[test] 22 | fn test_from_rgb_hex() { 23 | assert_eq!( 24 | crate::Color32::from_rgb(0x20, 0x21, 0x22), 25 | hex_color!("#202122") 26 | ); 27 | assert_eq!( 28 | crate::Color32::from_rgb_additive(0x20, 0x21, 0x22), 29 | hex_color!("#202122").additive() 30 | ); 31 | } 32 | 33 | #[test] 34 | fn test_from_rgba_hex() { 35 | assert_eq!( 36 | crate::Color32::from_rgba_unmultiplied(0x20, 0x21, 0x22, 0x50), 37 | hex_color!("20212250") 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /crates/ecolor/src/hsva_gamma.rs: -------------------------------------------------------------------------------- 1 | use crate::{gamma_from_linear, linear_from_gamma, Color32, Hsva, Rgba}; 2 | 3 | /// Like Hsva but with the `v` value (brightness) being gamma corrected 4 | /// so that it is somewhat perceptually even. 5 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 6 | pub struct HsvaGamma { 7 | /// hue 0-1 8 | pub h: f32, 9 | 10 | /// saturation 0-1 11 | pub s: f32, 12 | 13 | /// value 0-1, in gamma-space (~perceptually even) 14 | pub v: f32, 15 | 16 | /// alpha 0-1. A negative value signifies an additive color (and alpha is ignored). 17 | pub a: f32, 18 | } 19 | 20 | impl From for Rgba { 21 | fn from(hsvag: HsvaGamma) -> Rgba { 22 | Hsva::from(hsvag).into() 23 | } 24 | } 25 | 26 | impl From for Color32 { 27 | fn from(hsvag: HsvaGamma) -> Color32 { 28 | Rgba::from(hsvag).into() 29 | } 30 | } 31 | 32 | impl From for Hsva { 33 | fn from(hsvag: HsvaGamma) -> Hsva { 34 | let HsvaGamma { h, s, v, a } = hsvag; 35 | Hsva { 36 | h, 37 | s, 38 | v: linear_from_gamma(v), 39 | a, 40 | } 41 | } 42 | } 43 | 44 | impl From for HsvaGamma { 45 | fn from(rgba: Rgba) -> HsvaGamma { 46 | Hsva::from(rgba).into() 47 | } 48 | } 49 | 50 | impl From for HsvaGamma { 51 | fn from(srgba: Color32) -> HsvaGamma { 52 | Hsva::from(srgba).into() 53 | } 54 | } 55 | 56 | impl From for HsvaGamma { 57 | fn from(hsva: Hsva) -> HsvaGamma { 58 | let Hsva { h, s, v, a } = hsva; 59 | HsvaGamma { 60 | h, 61 | s, 62 | v: gamma_from_linear(v), 63 | a, 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/eframe/README.md: -------------------------------------------------------------------------------- 1 | # eframe: the [`egui`](https://github.com/emilk/egui) framework 2 | 3 | [![Latest version](https://img.shields.io/crates/v/eframe.svg)](https://crates.io/crates/eframe) 4 | [![Documentation](https://docs.rs/eframe/badge.svg)](https://docs.rs/eframe) 5 | [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) 6 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 7 | ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) 8 | 9 | `eframe` is the official framework library for writing apps using [`egui`](https://github.com/emilk/egui). The app can be compiled both to run natively (cross platform) or be compiled to a web app (using WASM). 10 | 11 | To get started, see the [examples](https://github.com/emilk/egui/tree/master/examples). 12 | To learn how to set up `eframe` for web and native, go to and follow the instructions there! 13 | 14 | There is also a tutorial video at . 15 | 16 | For how to use `egui`, see [the egui docs](https://docs.rs/egui). 17 | 18 | --- 19 | 20 | `eframe` uses [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering, and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit). 21 | 22 | To use on Linux, first run: 23 | 24 | ``` 25 | sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev 26 | ``` 27 | 28 | You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info. 29 | 30 | You can opt-in to the using [`egui_wgpu`](https://github.com/emilk/egui/tree/master/crates/egui_wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`. 31 | 32 | 33 | ## Alternatives 34 | `eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others. 35 | 36 | You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/winit) to build your own app as demonstrated in . 37 | 38 | 39 | ## Problems with running egui on the web 40 | `eframe` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and WASM, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides. 41 | 42 | * Rendering: Getting pixel-perfect rendering right on the web is very difficult. 43 | * Search: you cannot search an egui web page like you would a normal web page. 44 | * Bringing up an on-screen keyboard on mobile: there is no JS function to do this, so `eframe` fakes it by adding some invisible DOM elements. It doesn't always work. 45 | * Mobile text editing is not as good as for a normal web app. 46 | * Accessibility: There is an experimental screen reader for `eframe`, but it has to be enabled explicitly. There is no JS function to ask "Does the user want a screen reader?" (and there should probably not be such a function, due to user tracking/integrity concerns). 47 | * No integration with browser settings for colors and fonts. 48 | 49 | In many ways, `eframe` is trying to make the browser do something it wasn't designed to do (though there are many things browser vendors could do to improve how well libraries like egui work). 50 | 51 | The suggested use for `eframe` are for web apps where performance and responsiveness are more important than accessibility and mobile text editing. 52 | 53 | 54 | ## Companion crates 55 | Not all rust crates work when compiled to WASM, but here are some useful crates have been designed to work well both natively and as WASM: 56 | 57 | * Audio: [`cpal`](https://github.com/RustAudio/cpal). 58 | * HTTP client: [`ehttp`](https://github.com/emilk/ehttp) and [`reqwest`](https://github.com/seanmonstar/reqwest). 59 | * Time: [`chrono`](https://github.com/chronotope/chrono). 60 | * WebSockets: [`ewebsock`](https://github.com/rerun-io/ewebsock). 61 | 62 | 63 | ## Name 64 | The _frame_ in `eframe` stands both for the frame in which your `egui` app resides and also for "framework" (`frame` is a framework, `egui` is a library). 65 | -------------------------------------------------------------------------------- /crates/eframe/src/native/file_storage.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | path::{Path, PathBuf}, 4 | }; 5 | 6 | // ---------------------------------------------------------------------------- 7 | 8 | /// A key-value store backed by a [RON](https://github.com/ron-rs/ron) file on disk. 9 | /// Used to restore egui state, glium window position/size and app state. 10 | pub struct FileStorage { 11 | ron_filepath: PathBuf, 12 | kv: HashMap, 13 | dirty: bool, 14 | last_save_join_handle: Option>, 15 | } 16 | 17 | impl Drop for FileStorage { 18 | fn drop(&mut self) { 19 | if let Some(join_handle) = self.last_save_join_handle.take() { 20 | join_handle.join().ok(); 21 | } 22 | } 23 | } 24 | 25 | impl FileStorage { 26 | /// Store the state in this .ron file. 27 | pub fn from_ron_filepath(ron_filepath: impl Into) -> Self { 28 | let ron_filepath: PathBuf = ron_filepath.into(); 29 | tracing::debug!("Loading app state from {:?}…", ron_filepath); 30 | Self { 31 | kv: read_ron(&ron_filepath).unwrap_or_default(), 32 | ron_filepath, 33 | dirty: false, 34 | last_save_join_handle: None, 35 | } 36 | } 37 | 38 | /// Find a good place to put the files that the OS likes. 39 | pub fn from_app_name(app_name: &str) -> Option { 40 | if let Some(proj_dirs) = directories_next::ProjectDirs::from("", "", app_name) { 41 | let data_dir = proj_dirs.data_dir().to_path_buf(); 42 | if let Err(err) = std::fs::create_dir_all(&data_dir) { 43 | tracing::warn!( 44 | "Saving disabled: Failed to create app path at {:?}: {}", 45 | data_dir, 46 | err 47 | ); 48 | None 49 | } else { 50 | Some(Self::from_ron_filepath(data_dir.join("app.ron"))) 51 | } 52 | } else { 53 | tracing::warn!("Saving disabled: Failed to find path to data_dir."); 54 | None 55 | } 56 | } 57 | } 58 | 59 | impl crate::Storage for FileStorage { 60 | fn get_string(&self, key: &str) -> Option { 61 | self.kv.get(key).cloned() 62 | } 63 | 64 | fn set_string(&mut self, key: &str, value: String) { 65 | if self.kv.get(key) != Some(&value) { 66 | self.kv.insert(key.to_owned(), value); 67 | self.dirty = true; 68 | } 69 | } 70 | 71 | fn flush(&mut self) { 72 | if self.dirty { 73 | self.dirty = false; 74 | 75 | let file_path = self.ron_filepath.clone(); 76 | let kv = self.kv.clone(); 77 | 78 | if let Some(join_handle) = self.last_save_join_handle.take() { 79 | // wait for previous save to complete. 80 | join_handle.join().ok(); 81 | } 82 | 83 | let join_handle = std::thread::spawn(move || { 84 | let file = std::fs::File::create(&file_path).unwrap(); 85 | let config = Default::default(); 86 | ron::ser::to_writer_pretty(file, &kv, config).unwrap(); 87 | tracing::trace!("Persisted to {:?}", file_path); 88 | }); 89 | 90 | self.last_save_join_handle = Some(join_handle); 91 | } 92 | } 93 | } 94 | 95 | // ---------------------------------------------------------------------------- 96 | 97 | fn read_ron(ron_path: impl AsRef) -> Option 98 | where 99 | T: serde::de::DeserializeOwned, 100 | { 101 | match std::fs::File::open(ron_path) { 102 | Ok(file) => { 103 | let reader = std::io::BufReader::new(file); 104 | match ron::de::from_reader(reader) { 105 | Ok(value) => Some(value), 106 | Err(err) => { 107 | tracing::warn!("Failed to parse RON: {}", err); 108 | None 109 | } 110 | } 111 | } 112 | Err(_err) => { 113 | // File probably doesn't exist. That's fine. 114 | None 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /crates/eframe/src/native/mod.rs: -------------------------------------------------------------------------------- 1 | mod epi_integration; 2 | pub mod run; 3 | 4 | /// File storage which can be used by native backends. 5 | #[cfg(feature = "persistence")] 6 | pub mod file_storage; 7 | -------------------------------------------------------------------------------- /crates/eframe/src/web/screen_reader.rs: -------------------------------------------------------------------------------- 1 | pub struct ScreenReader { 2 | #[cfg(feature = "tts")] 3 | tts: Option, 4 | } 5 | 6 | #[cfg(not(feature = "tts"))] 7 | #[allow(clippy::derivable_impls)] // False positive 8 | impl Default for ScreenReader { 9 | fn default() -> Self { 10 | Self {} 11 | } 12 | } 13 | 14 | #[cfg(feature = "tts")] 15 | impl Default for ScreenReader { 16 | fn default() -> Self { 17 | let tts = match tts::Tts::default() { 18 | Ok(screen_reader) => { 19 | tracing::debug!("Initialized screen reader."); 20 | Some(screen_reader) 21 | } 22 | Err(err) => { 23 | tracing::warn!("Failed to load screen reader: {}", err); 24 | None 25 | } 26 | }; 27 | Self { tts } 28 | } 29 | } 30 | 31 | impl ScreenReader { 32 | #[cfg(not(feature = "tts"))] 33 | #[allow(clippy::unused_self)] 34 | pub fn speak(&mut self, _text: &str) {} 35 | 36 | #[cfg(feature = "tts")] 37 | pub fn speak(&mut self, text: &str) { 38 | if text.is_empty() { 39 | return; 40 | } 41 | if let Some(tts) = &mut self.tts { 42 | tracing::debug!("Speaking: {:?}", text); 43 | let interrupt = true; 44 | if let Err(err) = tts.speak(text, interrupt) { 45 | tracing::warn!("Failed to read: {}", err); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/eframe/src/web/storage.rs: -------------------------------------------------------------------------------- 1 | fn local_storage() -> Option { 2 | web_sys::window()?.local_storage().ok()? 3 | } 4 | 5 | pub fn local_storage_get(key: &str) -> Option { 6 | local_storage().map(|storage| storage.get_item(key).ok())?? 7 | } 8 | 9 | pub fn local_storage_set(key: &str, value: &str) { 10 | local_storage().map(|storage| storage.set_item(key, value)); 11 | } 12 | 13 | #[cfg(feature = "persistence")] 14 | pub fn load_memory(ctx: &egui::Context) { 15 | if let Some(memory_string) = local_storage_get("egui_memory_ron") { 16 | match ron::from_str(&memory_string) { 17 | Ok(memory) => { 18 | ctx.memory_mut(|m| *m = memory); 19 | } 20 | Err(err) => { 21 | tracing::error!("Failed to parse memory RON: {}", err); 22 | } 23 | } 24 | } 25 | } 26 | 27 | #[cfg(not(feature = "persistence"))] 28 | pub fn load_memory(_: &egui::Context) {} 29 | 30 | #[cfg(feature = "persistence")] 31 | pub fn save_memory(ctx: &egui::Context) { 32 | match ctx.memory(|mem| ron::to_string(mem)) { 33 | Ok(ron) => { 34 | local_storage_set("egui_memory_ron", &ron); 35 | } 36 | Err(err) => { 37 | tracing::error!("Failed to serialize memory as RON: {}", err); 38 | } 39 | } 40 | } 41 | 42 | #[cfg(not(feature = "persistence"))] 43 | pub fn save_memory(_: &egui::Context) {} 44 | -------------------------------------------------------------------------------- /crates/eframe/src/web/web_painter.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::JsValue; 2 | 3 | /// Renderer for a browser canvas. 4 | /// As of writing we're not allowing to decide on the painter at runtime, 5 | /// therefore this trait is merely there for specifying and documenting the interface. 6 | pub(crate) trait WebPainter { 7 | // Create a new web painter targeting a given canvas. 8 | // fn new(canvas_id: &str, options: &WebOptions) -> Result 9 | // where 10 | // Self: Sized; 11 | 12 | /// Id of the canvas in use. 13 | fn canvas_id(&self) -> &str; 14 | 15 | /// Maximum size of a texture in one direction. 16 | fn max_texture_side(&self) -> usize; 17 | 18 | /// Update all internal textures and paint gui. 19 | fn paint_and_update_textures( 20 | &mut self, 21 | clear_color: [f32; 4], 22 | clipped_primitives: &[egui::ClippedPrimitive], 23 | pixels_per_point: f32, 24 | textures_delta: &egui::TexturesDelta, 25 | ) -> Result<(), JsValue>; 26 | 27 | /// Destroy all resources. 28 | fn destroy(&mut self); 29 | } 30 | -------------------------------------------------------------------------------- /crates/egui-wgpu/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for egui-wgpu 2 | All notable changes to the `egui-wgpu` integration will be noted in this file. 3 | 4 | 5 | ## Unreleased 6 | * update to wgpu 0.15 ([#2629](https://github.com/emilk/egui/pull/2629)) 7 | * Return `Err` instead of panic if we can't find a device ([#2428](https://github.com/emilk/egui/pull/2428)). 8 | * `winit::Painter::set_window` is now `async` ([#2434](https://github.com/emilk/egui/pull/2434)). 9 | * `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)). 10 | 11 | 12 | ## 0.20.0 - 2022-12-08 - web support 13 | * Renamed `RenderPass` to `Renderer`. 14 | * Renamed `RenderPass::execute` to `RenderPass::render`. 15 | * Renamed `RenderPass::execute_with_renderpass` to `Renderer::render` (replacing existing `Renderer::render`) 16 | * Reexported `Renderer`. 17 | * You can now use `egui-wgpu` on web, using WebGL ([#2107](https://github.com/emilk/egui/pull/2107)). 18 | * `Renderer` no longer handles pass creation and depth buffer creation ([#2136](https://github.com/emilk/egui/pull/2136)) 19 | * `PrepareCallback` now passes `wgpu::CommandEncoder` ([#2136](https://github.com/emilk/egui/pull/2136)) 20 | * `PrepareCallback` can now returns `wgpu::CommandBuffer` that are bundled into a single `wgpu::Queue::submit` call ([#2230](https://github.com/emilk/egui/pull/2230)) 21 | * Only a single vertex & index buffer is now created and resized when necessary (previously, vertex/index buffers were allocated for every mesh) ([#2148](https://github.com/emilk/egui/pull/2148)). 22 | * `Renderer::update_texture` no longer creates a new `wgpu::Sampler` with every new texture ([#2198](https://github.com/emilk/egui/pull/2198)) 23 | * `Painter`'s instance/device/adapter/surface creation is now configurable via `WgpuConfiguration` ([#2207](https://github.com/emilk/egui/pull/2207)) 24 | * Fix panic on using a depth buffer ([#2316](https://github.com/emilk/egui/pull/2316)) 25 | 26 | 27 | ## 0.19.0 - 2022-08-20 28 | * Enables deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)). 29 | * Make `RenderPass` `Send` and `Sync` ([#1883](https://github.com/emilk/egui/pull/1883)). 30 | 31 | 32 | ## 0.18.0 - 2022-05-15 33 | First published version since moving the code into the `egui` repository from . 34 | -------------------------------------------------------------------------------- /crates/egui-wgpu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui-wgpu" 3 | version = "0.20.0" 4 | description = "Bindings for using egui natively using the wgpu library" 5 | authors = [ 6 | "Nils Hasenbanck ", 7 | "embotech ", 8 | "Emil Ernerfeldt ", 9 | ] 10 | edition = "2021" 11 | rust-version = "1.65" 12 | homepage = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu" 13 | license = "MIT OR Apache-2.0" 14 | readme = "README.md" 15 | repository = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu" 16 | categories = ["gui", "game-development"] 17 | keywords = ["wgpu", "egui", "gui", "gamedev"] 18 | include = [ 19 | "../LICENSE-APACHE", 20 | "../LICENSE-MIT", 21 | "**/*.rs", 22 | "**/*.wgsl", 23 | "Cargo.toml", 24 | ] 25 | 26 | [package.metadata.docs.rs] 27 | all-features = true 28 | 29 | 30 | [features] 31 | ## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate. 32 | puffin = ["dep:puffin"] 33 | 34 | ## Enable [`winit`](https://docs.rs/winit) integration. 35 | winit = ["dep:winit"] 36 | 37 | 38 | [dependencies] 39 | epaint = { version = "0.20.0", path = "../epaint", default-features = false, features = [ 40 | "bytemuck", 41 | ] } 42 | 43 | bytemuck = "1.7" 44 | tracing = { version = "0.1", default-features = false, features = ["std"] } 45 | type-map = "0.5.0" 46 | wgpu = "0.15.0" 47 | 48 | #! ### Optional dependencies 49 | ## Enable this when generating docs. 50 | document-features = { version = "0.2", optional = true } 51 | 52 | winit = { version = "0.27.2", optional = true } 53 | 54 | # Native: 55 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 56 | puffin = { version = "0.14", optional = true } 57 | -------------------------------------------------------------------------------- /crates/egui-wgpu/README.md: -------------------------------------------------------------------------------- 1 | # egui-wgpu 2 | 3 | [![Latest version](https://img.shields.io/crates/v/egui-wgpu.svg)](https://crates.io/crates/egui-wgpu) 4 | [![Documentation](https://docs.rs/egui-wgpu/badge.svg)](https://docs.rs/egui-wgpu) 5 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 6 | ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) 7 | 8 | This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu). 9 | 10 | This was originally hosted at https://github.com/hasenbanck/egui_wgpu_backend 11 | -------------------------------------------------------------------------------- /crates/egui-wgpu/src/egui.wgsl: -------------------------------------------------------------------------------- 1 | // Vertex shader bindings 2 | 3 | struct VertexOutput { 4 | @location(0) tex_coord: vec2, 5 | @location(1) color: vec4, // gamma 0-1 6 | @builtin(position) position: vec4, 7 | }; 8 | 9 | struct Locals { 10 | screen_size: vec2, 11 | // Uniform buffers need to be at least 16 bytes in WebGL. 12 | // See https://github.com/gfx-rs/wgpu/issues/2072 13 | _padding: vec2, 14 | }; 15 | @group(0) @binding(0) var r_locals: Locals; 16 | 17 | // 0-1 linear from 0-1 sRGB gamma 18 | fn linear_from_gamma_rgb(srgb: vec3) -> vec3 { 19 | let cutoff = srgb < vec3(0.04045); 20 | let lower = srgb / vec3(12.92); 21 | let higher = pow((srgb + vec3(0.055)) / vec3(1.055), vec3(2.4)); 22 | return select(higher, lower, cutoff); 23 | } 24 | 25 | // 0-1 sRGB gamma from 0-1 linear 26 | fn gamma_from_linear_rgb(rgb: vec3) -> vec3 { 27 | let cutoff = rgb < vec3(0.0031308); 28 | let lower = rgb * vec3(12.92); 29 | let higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055); 30 | return select(higher, lower, cutoff); 31 | } 32 | 33 | // 0-1 sRGBA gamma from 0-1 linear 34 | fn gamma_from_linear_rgba(linear_rgba: vec4) -> vec4 { 35 | return vec4(gamma_from_linear_rgb(linear_rgba.rgb), linear_rgba.a); 36 | } 37 | 38 | // [u8; 4] SRGB as u32 -> [r, g, b, a] in 0.-1 39 | fn unpack_color(color: u32) -> vec4 { 40 | return vec4( 41 | f32(color & 255u), 42 | f32((color >> 8u) & 255u), 43 | f32((color >> 16u) & 255u), 44 | f32((color >> 24u) & 255u), 45 | ) / 255.0; 46 | } 47 | 48 | fn position_from_screen(screen_pos: vec2) -> vec4 { 49 | return vec4( 50 | 2.0 * screen_pos.x / r_locals.screen_size.x - 1.0, 51 | 1.0 - 2.0 * screen_pos.y / r_locals.screen_size.y, 52 | 0.0, 53 | 1.0, 54 | ); 55 | } 56 | 57 | @vertex 58 | fn vs_main( 59 | @location(0) a_pos: vec2, 60 | @location(1) a_tex_coord: vec2, 61 | @location(2) a_color: u32, 62 | ) -> VertexOutput { 63 | var out: VertexOutput; 64 | out.tex_coord = a_tex_coord; 65 | out.color = unpack_color(a_color); 66 | out.position = position_from_screen(a_pos); 67 | return out; 68 | } 69 | 70 | // Fragment shader bindings 71 | 72 | @group(1) @binding(0) var r_tex_color: texture_2d; 73 | @group(1) @binding(1) var r_tex_sampler: sampler; 74 | 75 | @fragment 76 | fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { 77 | // We always have an sRGB aware texture at the moment. 78 | let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); 79 | let tex_gamma = gamma_from_linear_rgba(tex_linear); 80 | let out_color_gamma = in.color * tex_gamma; 81 | return vec4(linear_from_gamma_rgb(out_color_gamma.rgb), out_color_gamma.a); 82 | } 83 | 84 | @fragment 85 | fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4 { 86 | // We always have an sRGB aware texture at the moment. 87 | let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); 88 | let tex_gamma = gamma_from_linear_rgba(tex_linear); 89 | let out_color_gamma = in.color * tex_gamma; 90 | return out_color_gamma; 91 | } 92 | -------------------------------------------------------------------------------- /crates/egui-winit/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for egui-winit 2 | All notable changes to the `egui-winit` integration will be noted in this file. 3 | 4 | 5 | ## Unreleased 6 | * Remove the `screen_reader` feature. Use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)). 7 | 8 | 9 | ## 0.20.1 - 2022-12-11 10 | * Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)). 11 | 12 | 13 | ## 0.20.0 - 2022-12-08 14 | * The default features of the `winit` crate are not enabled if the default features of `egui-winit` are disabled too ([#1971](https://github.com/emilk/egui/pull/1971)). 15 | * Added new feature `wayland` which enables Wayland support ([#1971](https://github.com/emilk/egui/pull/1971)). 16 | * Don't repaint when just moving window ([#1980](https://github.com/emilk/egui/pull/1980)). 17 | * Added optional integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)). 18 | 19 | ## 0.19.0 - 2022-08-20 20 | * MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)). 21 | * Fixed clipboard on Wayland ([#1613](https://github.com/emilk/egui/pull/1613)). 22 | * Allow deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)). 23 | * Fixed window position persistence ([#1745](https://github.com/emilk/egui/pull/1745)). 24 | * Fixed mouse cursor change on Linux ([#1747](https://github.com/emilk/egui/pull/1747)). 25 | * Use the new `RawInput::has_focus` field to indicate whether the window has the keyboard focus ([#1859](https://github.com/emilk/egui/pull/1859)). 26 | 27 | 28 | ## 0.18.0 - 2022-04-30 29 | * Reexport `egui` crate 30 | * MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)). 31 | * Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)). 32 | * Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)). 33 | * Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)). 34 | * Removed the features `dark-light` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)). 35 | 36 | 37 | ## 0.17.0 - 2022-02-22 38 | * Fixed horizontal scrolling direction on Linux. 39 | * Replaced `std::time::Instant` with `instant::Instant` for WebAssembly compatability ([#1023](https://github.com/emilk/egui/pull/1023)) 40 | * Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)). 41 | * Fixed `enable_drag` on Windows OS ([#1108](https://github.com/emilk/egui/pull/1108)). 42 | * Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)). 43 | * Require knowledge about max texture side (e.g. `GL_MAX_TEXTURE_SIZE`)) ([#1154](https://github.com/emilk/egui/pull/1154)). 44 | 45 | 46 | ## 0.16.0 - 2021-12-29 47 | * Added helper `EpiIntegration` ([#871](https://github.com/emilk/egui/pull/871)). 48 | * Fixed shift key getting stuck enabled with the X11 option `shift:both_capslock` enabled ([#849](https://github.com/emilk/egui/pull/849)). 49 | * Removed `State::is_quit_event` and `State::is_quit_shortcut` ([#881](https://github.com/emilk/egui/pull/881)). 50 | * Updated `winit` to 0.26 ([#930](https://github.com/emilk/egui/pull/930)). 51 | 52 | 53 | ## 0.15.0 - 2021-10-24 54 | First stand-alone release. Previously part of `egui_glium`. 55 | -------------------------------------------------------------------------------- /crates/egui-winit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui-winit" 3 | version = "0.20.1" 4 | authors = ["Emil Ernerfeldt "] 5 | description = "Bindings for using egui with winit" 6 | edition = "2021" 7 | rust-version = "1.65" 8 | homepage = "https://github.com/emilk/egui/tree/master/crates/egui-winit" 9 | license = "MIT OR Apache-2.0" 10 | readme = "README.md" 11 | repository = "https://github.com/emilk/egui/tree/master/crates/egui-winit" 12 | categories = ["gui", "game-development"] 13 | keywords = ["winit", "egui", "gui", "gamedev"] 14 | include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] 15 | 16 | [package.metadata.docs.rs] 17 | all-features = true 18 | 19 | 20 | [features] 21 | default = ["clipboard", "links", "wayland", "winit/default"] 22 | 23 | ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). 24 | accesskit = ["accesskit_winit", "egui/accesskit"] 25 | 26 | ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`egui::epaint::Vertex`], [`egui::Vec2`] etc to `&[u8]`. 27 | bytemuck = ["egui/bytemuck"] 28 | 29 | ## Enable cut/copy/paste to OS clipboard. 30 | ## If disabled a clipboard will be simulated so you can still copy/paste within the egui app. 31 | clipboard = ["arboard", "smithay-clipboard"] 32 | 33 | ## Enable opening links in a browser when an egui hyperlink is clicked. 34 | links = ["webbrowser"] 35 | 36 | ## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate. 37 | puffin = ["dep:puffin"] 38 | 39 | ## Allow serialization of [`WindowSettings`] using [`serde`](https://docs.rs/serde). 40 | serde = ["egui/serde", "dep:serde"] 41 | 42 | ## Enables Wayland support. 43 | wayland = ["winit/wayland"] 44 | 45 | [dependencies] 46 | egui = { version = "0.20.0", path = "../egui", default-features = false, features = [ 47 | "tracing", 48 | ] } 49 | instant = { version = "0.1", features = [ 50 | "wasm-bindgen", 51 | ] } # We use instant so we can (maybe) compile for web 52 | tracing = { version = "0.1", default-features = false, features = ["std"] } 53 | winit = { version = "0.27.2", default-features = false } 54 | 55 | #! ### Optional dependencies 56 | 57 | ## Enable this when generating docs. 58 | document-features = { version = "0.2", optional = true } 59 | 60 | # feature accesskit 61 | accesskit_winit = { version = "0.8.1", optional = true } 62 | 63 | puffin = { version = "0.14", optional = true } 64 | serde = { version = "1.0", optional = true, features = ["derive"] } 65 | 66 | webbrowser = { version = "0.8.3", optional = true } 67 | 68 | [target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] 69 | smithay-clipboard = { version = "0.6.3", optional = true } 70 | 71 | [target.'cfg(not(target_os = "android"))'.dependencies] 72 | arboard = { version = "3.2", optional = true, default-features = false } 73 | -------------------------------------------------------------------------------- /crates/egui-winit/README.md: -------------------------------------------------------------------------------- 1 | # egui-winit 2 | 3 | [![Latest version](https://img.shields.io/crates/v/egui-winit.svg)](https://crates.io/crates/egui-winit) 4 | [![Documentation](https://docs.rs/egui-winit/badge.svg)](https://docs.rs/egui-winit) 5 | [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) 6 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 7 | ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) 8 | 9 | This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [`winit`](https://crates.io/crates/winit). 10 | 11 | The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc. 12 | -------------------------------------------------------------------------------- /crates/egui-winit/src/window_settings.rs: -------------------------------------------------------------------------------- 1 | /// Can be used to store native window settings (position and size). 2 | #[derive(Clone, Copy, Debug)] 3 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 4 | pub struct WindowSettings { 5 | /// Position of window in physical pixels. This is either 6 | /// the inner or outer position depending on the platform. 7 | /// See [`winit::window::WindowBuilder::with_position`] for details. 8 | position: Option, 9 | 10 | fullscreen: bool, 11 | 12 | /// Inner size of window in logical pixels 13 | inner_size_points: Option, 14 | } 15 | 16 | impl WindowSettings { 17 | pub fn from_display(window: &winit::window::Window) -> Self { 18 | let inner_size_points = window.inner_size().to_logical::(window.scale_factor()); 19 | let position = if cfg!(macos) { 20 | // MacOS uses inner position when positioning windows. 21 | window 22 | .inner_position() 23 | .ok() 24 | .map(|p| egui::pos2(p.x as f32, p.y as f32)) 25 | } else { 26 | // Other platforms use the outer position. 27 | window 28 | .outer_position() 29 | .ok() 30 | .map(|p| egui::pos2(p.x as f32, p.y as f32)) 31 | }; 32 | 33 | Self { 34 | position, 35 | 36 | fullscreen: window.fullscreen().is_some(), 37 | 38 | inner_size_points: Some(egui::vec2( 39 | inner_size_points.width, 40 | inner_size_points.height, 41 | )), 42 | } 43 | } 44 | 45 | pub fn inner_size_points(&self) -> Option { 46 | self.inner_size_points 47 | } 48 | 49 | pub fn initialize_window( 50 | &self, 51 | mut window: winit::window::WindowBuilder, 52 | ) -> winit::window::WindowBuilder { 53 | // If the app last ran on two monitors and only one is now connected, then 54 | // the given position is invalid. 55 | // If this happens on Mac, the window is clamped into valid area. 56 | // If this happens on Windows, the window is hidden and very difficult to find. 57 | // So we don't restore window positions on Windows. 58 | let try_restore_position = !cfg!(target_os = "windows"); 59 | if try_restore_position { 60 | if let Some(pos) = self.position { 61 | window = window.with_position(winit::dpi::PhysicalPosition { 62 | x: pos.x as f64, 63 | y: pos.y as f64, 64 | }); 65 | } 66 | } 67 | 68 | if let Some(inner_size_points) = self.inner_size_points { 69 | window 70 | .with_inner_size(winit::dpi::LogicalSize { 71 | width: inner_size_points.x as f64, 72 | height: inner_size_points.y as f64, 73 | }) 74 | .with_fullscreen( 75 | self.fullscreen 76 | .then_some(winit::window::Fullscreen::Borderless(None)), 77 | ) 78 | } else { 79 | window 80 | } 81 | } 82 | 83 | pub fn clamp_to_sane_values(&mut self, max_size: egui::Vec2) { 84 | use egui::NumExt as _; 85 | 86 | if let Some(size) = &mut self.inner_size_points { 87 | // Prevent ridiculously small windows 88 | let min_size = egui::Vec2::splat(64.0); 89 | *size = size.at_least(min_size); 90 | *size = size.at_most(max_size); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/egui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui" 3 | version = "0.20.1" 4 | authors = ["Emil Ernerfeldt "] 5 | description = "An easy-to-use immediate mode GUI that runs on both web and native" 6 | edition = "2021" 7 | rust-version = "1.65" 8 | homepage = "https://github.com/emilk/egui" 9 | license = "MIT OR Apache-2.0" 10 | readme = "../../README.md" 11 | repository = "https://github.com/emilk/egui" 12 | categories = ["gui", "game-development"] 13 | keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] 14 | include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] 15 | 16 | [package.metadata.docs.rs] 17 | all-features = true 18 | 19 | [lib] 20 | 21 | 22 | [features] 23 | default = ["default_fonts"] 24 | 25 | ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`. 26 | bytemuck = ["epaint/bytemuck"] 27 | 28 | ## [`cint`](https://docs.rs/cint) enables interopability with other color libraries. 29 | cint = ["epaint/cint"] 30 | 31 | ## Enable the [`hex_color`] macro. 32 | color-hex = ["epaint/color-hex"] 33 | 34 | ## This will automatically detect deadlocks due to double-locking on the same thread. 35 | ## If your app freezes, you may want to enable this! 36 | ## Only affects [`epaint::mutex::RwLock`] (which egui uses a lot). 37 | deadlock_detection = ["epaint/deadlock_detection"] 38 | 39 | ## If set, egui will use `include_bytes!` to bundle some fonts. 40 | ## If you plan on specifying your own fonts you may disable this feature. 41 | default_fonts = ["epaint/default_fonts"] 42 | 43 | ## Enable additional checks if debug assertions are enabled (debug builds). 44 | extra_debug_asserts = ["epaint/extra_debug_asserts"] 45 | ## Always enable additional checks. 46 | extra_asserts = ["epaint/extra_asserts"] 47 | 48 | ## [`mint`](https://docs.rs/mint) enables interopability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra). 49 | mint = ["epaint/mint"] 50 | 51 | ## Enable persistence of memory (window positions etc). 52 | persistence = ["serde", "epaint/serde", "ron"] 53 | 54 | ## Allow serialization using [`serde`](https://docs.rs/serde). 55 | serde = ["dep:serde", "epaint/serde", "accesskit?/serde"] 56 | 57 | [dependencies] 58 | epaint = { version = "0.20.0", path = "../epaint", default-features = false } 59 | 60 | ahash = { version = "0.8.1", default-features = false, features = [ 61 | "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead 62 | "std", 63 | ] } 64 | nohash-hasher = "0.2" 65 | 66 | #! ### Optional dependencies 67 | ## Exposes detailed accessibility implementation required by platform 68 | ## accessibility APIs. Also requires support in the egui integration. 69 | accesskit = { version = "0.8.1", optional = true } 70 | 71 | ## Enable this when generating docs. 72 | document-features = { version = "0.2", optional = true } 73 | 74 | ron = { version = "0.8", optional = true } 75 | serde = { version = "1", optional = true, features = ["derive", "rc"] } 76 | 77 | # egui doesn't log much, but when it does, it uses [`tracing`](https://docs.rs/tracing). 78 | tracing = { version = "0.1", optional = true, default-features = false, features = [ 79 | "std", 80 | ] } 81 | -------------------------------------------------------------------------------- /crates/egui/README.md: -------------------------------------------------------------------------------- 1 | # GUI implementation 2 | This is the core library crate egui. It is fully platform independent without any backend. You give the egui library input each frame (mouse pos etc), and it outputs a triangle mesh for you to paint. 3 | -------------------------------------------------------------------------------- /crates/egui/examples/README.md: -------------------------------------------------------------------------------- 1 | There are no stand-alone egui examples, because egui is not stand-alone! 2 | 3 | See the top-level [examples](https://github.com/emilk/egui/tree/master/examples/) folder instead. 4 | 5 | There are also plenty of examples in [the online demo](https://www.egui.rs/#demo). You can find the source code for it at . 6 | 7 | To learn how to set up `eframe` for web and native, go to and follow the instructions there! 8 | -------------------------------------------------------------------------------- /crates/egui/src/animation_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::{emath::remap_clamp, Id, IdMap, InputState}; 2 | 3 | #[derive(Clone, Default)] 4 | pub(crate) struct AnimationManager { 5 | bools: IdMap, 6 | values: IdMap, 7 | } 8 | 9 | #[derive(Clone, Debug)] 10 | struct BoolAnim { 11 | value: bool, 12 | /// when did `value` last toggle? 13 | toggle_time: f64, 14 | } 15 | 16 | #[derive(Clone, Debug)] 17 | struct ValueAnim { 18 | from_value: f32, 19 | to_value: f32, 20 | /// when did `value` last toggle? 21 | toggle_time: f64, 22 | } 23 | 24 | impl AnimationManager { 25 | /// See `Context::animate_bool` for documentation 26 | pub fn animate_bool( 27 | &mut self, 28 | input: &InputState, 29 | animation_time: f32, 30 | id: Id, 31 | value: bool, 32 | ) -> f32 { 33 | match self.bools.get_mut(&id) { 34 | None => { 35 | self.bools.insert( 36 | id, 37 | BoolAnim { 38 | value, 39 | toggle_time: -f64::INFINITY, // long time ago 40 | }, 41 | ); 42 | if value { 43 | 1.0 44 | } else { 45 | 0.0 46 | } 47 | } 48 | Some(anim) => { 49 | if anim.value != value { 50 | anim.value = value; 51 | anim.toggle_time = input.time; 52 | } 53 | 54 | let time_since_toggle = (input.time - anim.toggle_time) as f32; 55 | 56 | // On the frame we toggle we don't want to return the old value, 57 | // so we extrapolate forwards: 58 | let time_since_toggle = time_since_toggle + input.predicted_dt; 59 | 60 | if value { 61 | remap_clamp(time_since_toggle, 0.0..=animation_time, 0.0..=1.0) 62 | } else { 63 | remap_clamp(time_since_toggle, 0.0..=animation_time, 1.0..=0.0) 64 | } 65 | } 66 | } 67 | } 68 | 69 | pub fn animate_value( 70 | &mut self, 71 | input: &InputState, 72 | animation_time: f32, 73 | id: Id, 74 | value: f32, 75 | ) -> f32 { 76 | match self.values.get_mut(&id) { 77 | None => { 78 | self.values.insert( 79 | id, 80 | ValueAnim { 81 | from_value: value, 82 | to_value: value, 83 | toggle_time: -f64::INFINITY, // long time ago 84 | }, 85 | ); 86 | value 87 | } 88 | Some(anim) => { 89 | let time_since_toggle = (input.time - anim.toggle_time) as f32; 90 | // On the frame we toggle we don't want to return the old value, 91 | // so we extrapolate forwards: 92 | let time_since_toggle = time_since_toggle + input.predicted_dt; 93 | let current_value = remap_clamp( 94 | time_since_toggle, 95 | 0.0..=animation_time, 96 | anim.from_value..=anim.to_value, 97 | ); 98 | if anim.to_value != value { 99 | anim.from_value = current_value; //start new animation from current position of playing animation 100 | anim.to_value = value; 101 | anim.toggle_time = input.time; 102 | } 103 | if animation_time == 0.0 { 104 | anim.from_value = value; 105 | anim.to_value = value; 106 | } 107 | current_value 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/egui/src/containers/mod.rs: -------------------------------------------------------------------------------- 1 | //! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], [`SidePanel`], etc. 2 | //! 3 | //! For instance, a [`Frame`] adds a frame and background to some contained UI. 4 | 5 | pub(crate) mod area; 6 | pub mod collapsing_header; 7 | mod combo_box; 8 | pub(crate) mod frame; 9 | pub mod panel; 10 | pub mod popup; 11 | pub(crate) mod resize; 12 | pub mod scroll_area; 13 | pub(crate) mod window; 14 | 15 | pub use { 16 | area::Area, 17 | collapsing_header::{CollapsingHeader, CollapsingResponse}, 18 | combo_box::*, 19 | frame::Frame, 20 | panel::{CentralPanel, SidePanel, TopBottomPanel}, 21 | popup::*, 22 | resize::Resize, 23 | scroll_area::ScrollArea, 24 | window::Window, 25 | }; 26 | -------------------------------------------------------------------------------- /crates/egui/src/data/mod.rs: -------------------------------------------------------------------------------- 1 | //! All the data sent between egui and the backend 2 | 3 | pub mod input; 4 | pub mod output; 5 | -------------------------------------------------------------------------------- /crates/egui/src/gui_zoom.rs: -------------------------------------------------------------------------------- 1 | //! Helpers for zooming the whole GUI of an app (changing [`Context::pixels_per_point`]. 2 | //! 3 | use crate::*; 4 | 5 | /// The suggested keyboard shortcuts for global gui zooming. 6 | pub mod kb_shortcuts { 7 | use super::*; 8 | 9 | pub const ZOOM_IN: KeyboardShortcut = 10 | KeyboardShortcut::new(Modifiers::COMMAND, Key::PlusEquals); 11 | pub const ZOOM_OUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Minus); 12 | pub const ZOOM_RESET: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Num0); 13 | } 14 | 15 | /// Let the user scale the GUI (change `Context::pixels_per_point`) by pressing 16 | /// Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser. 17 | /// 18 | /// When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe), you want to call this as: 19 | /// ```ignore 20 | /// // On web, the browser controls the gui zoom. 21 | /// if !frame.is_web() { 22 | /// egui::gui_zoom::zoom_with_keyboard_shortcuts( 23 | /// ctx, 24 | /// frame.info().native_pixels_per_point, 25 | /// ); 26 | /// } 27 | /// ``` 28 | pub fn zoom_with_keyboard_shortcuts(ctx: &Context, native_pixels_per_point: Option) { 29 | if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_RESET)) { 30 | if let Some(native_pixels_per_point) = native_pixels_per_point { 31 | ctx.set_pixels_per_point(native_pixels_per_point); 32 | } 33 | } else { 34 | if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_IN)) { 35 | zoom_in(ctx); 36 | } 37 | if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_OUT)) { 38 | zoom_out(ctx); 39 | } 40 | } 41 | } 42 | 43 | const MIN_PIXELS_PER_POINT: f32 = 0.2; 44 | const MAX_PIXELS_PER_POINT: f32 = 4.0; 45 | 46 | /// Make everything larger. 47 | pub fn zoom_in(ctx: &Context) { 48 | let mut pixels_per_point = ctx.pixels_per_point(); 49 | pixels_per_point += 0.1; 50 | pixels_per_point = pixels_per_point.clamp(MIN_PIXELS_PER_POINT, MAX_PIXELS_PER_POINT); 51 | pixels_per_point = (pixels_per_point * 10.).round() / 10.; 52 | ctx.set_pixels_per_point(pixels_per_point); 53 | } 54 | 55 | /// Make everything smaller. 56 | pub fn zoom_out(ctx: &Context) { 57 | let mut pixels_per_point = ctx.pixels_per_point(); 58 | pixels_per_point -= 0.1; 59 | pixels_per_point = pixels_per_point.clamp(MIN_PIXELS_PER_POINT, MAX_PIXELS_PER_POINT); 60 | pixels_per_point = (pixels_per_point * 10.).round() / 10.; 61 | ctx.set_pixels_per_point(pixels_per_point); 62 | } 63 | 64 | /// Show buttons for zooming the ui. 65 | /// 66 | /// This is meant to be called from within a menu (See [`Ui::menu_button`]). 67 | /// 68 | /// When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe), you want to call this as: 69 | /// ```ignore 70 | /// // On web, the browser controls the gui zoom. 71 | /// if !frame.is_web() { 72 | /// ui.menu_button("View", |ui| { 73 | /// egui::gui_zoom::zoom_menu_buttons( 74 | /// ui, 75 | /// frame.info().native_pixels_per_point, 76 | /// ); 77 | /// }); 78 | /// } 79 | /// ``` 80 | pub fn zoom_menu_buttons(ui: &mut Ui, native_pixels_per_point: Option) { 81 | if ui 82 | .add_enabled( 83 | ui.ctx().pixels_per_point() < MAX_PIXELS_PER_POINT, 84 | Button::new("Zoom In").shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_IN)), 85 | ) 86 | .clicked() 87 | { 88 | zoom_in(ui.ctx()); 89 | ui.close_menu(); 90 | } 91 | 92 | if ui 93 | .add_enabled( 94 | ui.ctx().pixels_per_point() > MIN_PIXELS_PER_POINT, 95 | Button::new("Zoom Out") 96 | .shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_OUT)), 97 | ) 98 | .clicked() 99 | { 100 | zoom_out(ui.ctx()); 101 | ui.close_menu(); 102 | } 103 | 104 | if let Some(native_pixels_per_point) = native_pixels_per_point { 105 | if ui 106 | .add_enabled( 107 | ui.ctx().pixels_per_point() != native_pixels_per_point, 108 | Button::new("Reset Zoom") 109 | .shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_RESET)), 110 | ) 111 | .clicked() 112 | { 113 | ui.ctx().set_pixels_per_point(native_pixels_per_point); 114 | ui.close_menu(); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /crates/egui/src/os.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 2 | pub enum OperatingSystem { 3 | /// Unknown OS - could be wasm 4 | Unknown, 5 | 6 | /// Android OS. 7 | Android, 8 | 9 | /// Apple iPhone OS. 10 | IOS, 11 | 12 | /// Linux or Unix other than Android. 13 | Nix, 14 | 15 | /// MacOS. 16 | Mac, 17 | 18 | /// Windows. 19 | Windows, 20 | } 21 | 22 | impl Default for OperatingSystem { 23 | fn default() -> Self { 24 | Self::from_target_os() 25 | } 26 | } 27 | 28 | impl OperatingSystem { 29 | pub const fn from_target_os() -> Self { 30 | if cfg!(target_arch = "wasm32") { 31 | Self::Unknown 32 | } else if cfg!(target_os = "android") { 33 | Self::Android 34 | } else if cfg!(target_os = "ios") { 35 | Self::IOS 36 | } else if cfg!(target_os = "macos") { 37 | Self::Mac 38 | } else if cfg!(target_os = "windows") { 39 | Self::Android 40 | } else if cfg!(target_os = "linux") 41 | || cfg!(target_os = "dragonfly") 42 | || cfg!(target_os = "freebsd") 43 | || cfg!(target_os = "netbsd") 44 | || cfg!(target_os = "openbsd") 45 | { 46 | Self::Nix 47 | } else { 48 | Self::Unknown 49 | } 50 | } 51 | 52 | /// Helper: try to guess from the user-agent of a browser. 53 | pub fn from_user_agent(user_agent: &str) -> Self { 54 | if user_agent.contains("Android") { 55 | Self::Android 56 | } else if user_agent.contains("like Mac") { 57 | Self::IOS 58 | } else if user_agent.contains("Win") { 59 | Self::Windows 60 | } else if user_agent.contains("Mac") { 61 | Self::Mac 62 | } else if user_agent.contains("Linux") 63 | || user_agent.contains("X11") 64 | || user_agent.contains("Unix") 65 | { 66 | Self::Nix 67 | } else { 68 | #[cfg(feature = "tracing")] 69 | tracing::warn!( 70 | "egui: Failed to guess operating system from User-Agent {:?}. Please file an issue at https://github.com/emilk/egui/issues", 71 | user_agent); 72 | 73 | Self::Unknown 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /crates/egui/src/sense.rs: -------------------------------------------------------------------------------- 1 | /// What sort of interaction is a widget sensitive to? 2 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 3 | // #[cfg_attr(feature = "serde", derive(serde::Serialize))] 4 | pub struct Sense { 5 | /// buttons, sliders, windows, … 6 | pub click: bool, 7 | 8 | /// sliders, windows, scroll bars, scroll areas, … 9 | pub drag: bool, 10 | 11 | /// this widgets want focus. 12 | /// Anything interactive + labels that can be focused 13 | /// for the benefit of screen readers. 14 | pub focusable: bool, 15 | } 16 | 17 | impl Sense { 18 | /// Senses no clicks or drags. Only senses mouse hover. 19 | #[doc(alias = "none")] 20 | pub fn hover() -> Self { 21 | Self { 22 | click: false, 23 | drag: false, 24 | focusable: false, 25 | } 26 | } 27 | 28 | /// Senses no clicks or drags, but can be focused with the keyboard. 29 | /// Used for labels that can be focused for the benefit of screen readers. 30 | pub fn focusable_noninteractive() -> Self { 31 | Self { 32 | click: false, 33 | drag: false, 34 | focusable: true, 35 | } 36 | } 37 | 38 | /// Sense clicks and hover, but not drags. 39 | pub fn click() -> Self { 40 | Self { 41 | click: true, 42 | drag: false, 43 | focusable: true, 44 | } 45 | } 46 | 47 | /// Sense drags and hover, but not clicks. 48 | pub fn drag() -> Self { 49 | Self { 50 | click: false, 51 | drag: true, 52 | focusable: true, 53 | } 54 | } 55 | 56 | /// Sense both clicks, drags and hover (e.g. a slider or window). 57 | pub fn click_and_drag() -> Self { 58 | Self { 59 | click: true, 60 | drag: true, 61 | focusable: true, 62 | } 63 | } 64 | 65 | /// The logical "or" of two [`Sense`]s. 66 | #[must_use] 67 | pub fn union(self, other: Self) -> Self { 68 | Self { 69 | click: self.click | other.click, 70 | drag: self.drag | other.drag, 71 | focusable: self.focusable | other.focusable, 72 | } 73 | } 74 | 75 | /// Returns true if we sense either clicks or drags. 76 | pub fn interactive(&self) -> bool { 77 | self.click || self.drag 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/egui/src/util/fixed_cache.rs: -------------------------------------------------------------------------------- 1 | use epaint::util::hash; 2 | 3 | const FIXED_CACHE_SIZE: usize = 1024; // must be small for web/WASM build (for unknown reason) 4 | 5 | /// Very stupid/simple key-value cache. TODO(emilk): improve 6 | #[derive(Clone)] 7 | pub(crate) struct FixedCache([Option<(K, V)>; FIXED_CACHE_SIZE]); 8 | 9 | impl Default for FixedCache 10 | where 11 | K: Copy, 12 | V: Copy, 13 | { 14 | fn default() -> Self { 15 | Self([None; FIXED_CACHE_SIZE]) 16 | } 17 | } 18 | 19 | impl std::fmt::Debug for FixedCache { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | write!(f, "Cache") 22 | } 23 | } 24 | 25 | impl FixedCache 26 | where 27 | K: std::hash::Hash + PartialEq, 28 | { 29 | pub fn get(&self, key: &K) -> Option<&V> { 30 | let bucket = (hash(key) % (FIXED_CACHE_SIZE as u64)) as usize; 31 | match &self.0[bucket] { 32 | Some((k, v)) if k == key => Some(v), 33 | _ => None, 34 | } 35 | } 36 | 37 | pub fn set(&mut self, key: K, value: V) { 38 | let bucket = (hash(&key) % (FIXED_CACHE_SIZE as u64)) as usize; 39 | self.0[bucket] = Some((key, value)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/egui/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous tools used by the rest of egui. 2 | 3 | pub mod cache; 4 | pub(crate) mod fixed_cache; 5 | pub mod id_type_map; 6 | pub mod undoer; 7 | 8 | pub use id_type_map::IdTypeMap; 9 | 10 | pub use epaint::emath::History; 11 | pub use epaint::util::{hash, hash_with}; 12 | -------------------------------------------------------------------------------- /crates/egui/src/widgets/hyperlink.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// Clickable text, that looks like a hyperlink. 4 | /// 5 | /// To link to a web page, use [`Hyperlink`], [`Ui::hyperlink`] or [`Ui::hyperlink_to`]. 6 | /// 7 | /// See also [`Ui::link`]. 8 | /// 9 | /// ``` 10 | /// # egui::__run_test_ui(|ui| { 11 | /// // These are equivalent: 12 | /// if ui.link("Documentation").clicked() { 13 | /// // … 14 | /// } 15 | /// 16 | /// if ui.add(egui::Link::new("Documentation")).clicked() { 17 | /// // … 18 | /// } 19 | /// # }); 20 | /// ``` 21 | #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] 22 | pub struct Link { 23 | text: WidgetText, 24 | } 25 | 26 | impl Link { 27 | pub fn new(text: impl Into) -> Self { 28 | Self { text: text.into() } 29 | } 30 | } 31 | 32 | impl Widget for Link { 33 | fn ui(self, ui: &mut Ui) -> Response { 34 | let Link { text } = self; 35 | let label = Label::new(text).sense(Sense::click()); 36 | 37 | let (pos, text_galley, response) = label.layout_in_ui(ui); 38 | response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, text_galley.text())); 39 | 40 | if response.hovered() { 41 | ui.ctx().set_cursor_icon(CursorIcon::PointingHand); 42 | } 43 | 44 | if ui.is_rect_visible(response.rect) { 45 | let color = ui.visuals().hyperlink_color; 46 | let visuals = ui.style().interact(&response); 47 | 48 | let underline = if response.hovered() || response.has_focus() { 49 | Stroke::new(visuals.fg_stroke.width, color) 50 | } else { 51 | Stroke::NONE 52 | }; 53 | 54 | ui.painter().add(epaint::TextShape { 55 | pos, 56 | galley: text_galley.galley, 57 | override_text_color: Some(color), 58 | underline, 59 | angle: 0.0, 60 | }); 61 | } 62 | 63 | response 64 | } 65 | } 66 | 67 | /// A clickable hyperlink, e.g. to `"https://github.com/emilk/egui"`. 68 | /// 69 | /// See also [`Ui::hyperlink`] and [`Ui::hyperlink_to`]. 70 | /// 71 | /// ``` 72 | /// # egui::__run_test_ui(|ui| { 73 | /// // These are equivalent: 74 | /// ui.hyperlink("https://github.com/emilk/egui"); 75 | /// ui.add(egui::Hyperlink::new("https://github.com/emilk/egui")); 76 | /// 77 | /// // These are equivalent: 78 | /// ui.hyperlink_to("My favorite repo", "https://github.com/emilk/egui"); 79 | /// ui.add(egui::Hyperlink::from_label_and_url("My favorite repo", "https://github.com/emilk/egui")); 80 | /// # }); 81 | /// ``` 82 | #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] 83 | pub struct Hyperlink { 84 | url: String, 85 | text: WidgetText, 86 | } 87 | 88 | impl Hyperlink { 89 | #[allow(clippy::needless_pass_by_value)] 90 | pub fn new(url: impl ToString) -> Self { 91 | let url = url.to_string(); 92 | Self { 93 | url: url.clone(), 94 | text: url.into(), 95 | } 96 | } 97 | 98 | #[allow(clippy::needless_pass_by_value)] 99 | pub fn from_label_and_url(text: impl Into, url: impl ToString) -> Self { 100 | Self { 101 | url: url.to_string(), 102 | text: text.into(), 103 | } 104 | } 105 | } 106 | 107 | impl Widget for Hyperlink { 108 | fn ui(self, ui: &mut Ui) -> Response { 109 | let Self { url, text } = self; 110 | 111 | let response = ui.add(Link::new(text)); 112 | if response.clicked() { 113 | let modifiers = ui.ctx().input(|i| i.modifiers); 114 | ui.ctx().output_mut(|o| { 115 | o.open_url = Some(crate::output::OpenUrl { 116 | url: url.clone(), 117 | new_tab: modifiers.any(), 118 | }); 119 | }); 120 | } 121 | if response.middle_clicked() { 122 | ui.ctx().output_mut(|o| { 123 | o.open_url = Some(crate::output::OpenUrl { 124 | url: url.clone(), 125 | new_tab: true, 126 | }); 127 | }); 128 | } 129 | response.on_hover_text(url) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /crates/egui/src/widgets/plot/items/rect_elem.rs: -------------------------------------------------------------------------------- 1 | use super::{Orientation, PlotPoint}; 2 | use crate::plot::transform::{PlotBounds, ScreenTransform}; 3 | use epaint::emath::NumExt; 4 | use epaint::{Color32, Rgba, Stroke}; 5 | 6 | /// Trait that abstracts from rectangular 'Value'-like elements, such as bars or boxes 7 | pub(super) trait RectElement { 8 | fn name(&self) -> &str; 9 | 10 | fn bounds_min(&self) -> PlotPoint; 11 | 12 | fn bounds_max(&self) -> PlotPoint; 13 | 14 | fn bounds(&self) -> PlotBounds { 15 | let mut bounds = PlotBounds::NOTHING; 16 | bounds.extend_with(&self.bounds_min()); 17 | bounds.extend_with(&self.bounds_max()); 18 | bounds 19 | } 20 | 21 | /// At which argument (input; usually X) there is a ruler (usually vertical) 22 | fn arguments_with_ruler(&self) -> Vec { 23 | // Default: one at center 24 | vec![self.bounds().center()] 25 | } 26 | 27 | /// At which value (output; usually Y) there is a ruler (usually horizontal) 28 | fn values_with_ruler(&self) -> Vec; 29 | 30 | /// The diagram's orientation (vertical/horizontal) 31 | fn orientation(&self) -> Orientation; 32 | 33 | /// Get X/Y-value for (argument, value) pair, taking into account orientation 34 | fn point_at(&self, argument: f64, value: f64) -> PlotPoint { 35 | match self.orientation() { 36 | Orientation::Horizontal => PlotPoint::new(value, argument), 37 | Orientation::Vertical => PlotPoint::new(argument, value), 38 | } 39 | } 40 | 41 | /// Right top of the rectangle (position of text) 42 | fn corner_value(&self) -> PlotPoint { 43 | //self.point_at(self.position + self.width / 2.0, value) 44 | PlotPoint { 45 | x: self.bounds_max().x, 46 | y: self.bounds_max().y, 47 | } 48 | } 49 | 50 | /// Debug formatting for hovered-over value, if none is specified by the user 51 | fn default_values_format(&self, transform: &ScreenTransform) -> String; 52 | } 53 | 54 | // ---------------------------------------------------------------------------- 55 | // Helper functions 56 | 57 | pub(super) fn highlighted_color(mut stroke: Stroke, fill: Color32) -> (Stroke, Color32) { 58 | stroke.width *= 2.0; 59 | let fill = Rgba::from(fill); 60 | let fill_alpha = (2.0 * fill.a()).at_most(1.0); 61 | let fill = fill.to_opaque().multiply(fill_alpha); 62 | (stroke, fill.into()) 63 | } 64 | -------------------------------------------------------------------------------- /crates/egui/src/widgets/selected_label.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// One out of several alternatives, either selected or not. 4 | /// Will mark selected items with a different background color. 5 | /// An alternative to [`RadioButton`] and [`Checkbox`]. 6 | /// 7 | /// Usually you'd use [`Ui::selectable_value`] or [`Ui::selectable_label`] instead. 8 | /// 9 | /// ``` 10 | /// # egui::__run_test_ui(|ui| { 11 | /// #[derive(PartialEq)] 12 | /// enum Enum { First, Second, Third } 13 | /// let mut my_enum = Enum::First; 14 | /// 15 | /// ui.selectable_value(&mut my_enum, Enum::First, "First"); 16 | /// 17 | /// // is equivalent to: 18 | /// 19 | /// if ui.add(egui::SelectableLabel::new(my_enum == Enum::First, "First")).clicked() { 20 | /// my_enum = Enum::First 21 | /// } 22 | /// # }); 23 | /// ``` 24 | #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] 25 | pub struct SelectableLabel { 26 | selected: bool, 27 | text: WidgetText, 28 | } 29 | 30 | impl SelectableLabel { 31 | pub fn new(selected: bool, text: impl Into) -> Self { 32 | Self { 33 | selected, 34 | text: text.into(), 35 | } 36 | } 37 | } 38 | 39 | impl Widget for SelectableLabel { 40 | fn ui(self, ui: &mut Ui) -> Response { 41 | let Self { selected, text } = self; 42 | 43 | let button_padding = ui.spacing().button_padding; 44 | let total_extra = button_padding + button_padding; 45 | 46 | let wrap_width = ui.available_width() - total_extra.x; 47 | let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); 48 | 49 | let mut desired_size = total_extra + text.size(); 50 | desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y); 51 | let (rect, response) = ui.allocate_at_least(desired_size, Sense::click()); 52 | response.widget_info(|| { 53 | WidgetInfo::selected(WidgetType::SelectableLabel, selected, text.text()) 54 | }); 55 | 56 | if ui.is_rect_visible(response.rect) { 57 | let text_pos = ui 58 | .layout() 59 | .align_size_within_rect(text.size(), rect.shrink2(button_padding)) 60 | .min; 61 | 62 | let visuals = ui.style().interact_selectable(&response, selected); 63 | 64 | if selected || response.hovered() || response.highlighted() || response.has_focus() { 65 | let rect = rect.expand(visuals.expansion); 66 | 67 | ui.painter().rect( 68 | rect, 69 | visuals.rounding, 70 | visuals.weak_bg_fill, 71 | visuals.bg_stroke, 72 | ); 73 | } 74 | 75 | text.paint_with_visuals(ui.painter(), text_pos, &visuals); 76 | } 77 | 78 | response 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /crates/egui/src/widgets/separator.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// A visual separator. A horizontal or vertical line (depending on [`Layout`]). 4 | /// 5 | /// Usually you'd use the shorter version [`Ui::separator`]. 6 | /// 7 | /// ``` 8 | /// # egui::__run_test_ui(|ui| { 9 | /// // These are equivalent: 10 | /// ui.separator(); 11 | /// ui.add(egui::Separator::default()); 12 | /// # }); 13 | /// ``` 14 | #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] 15 | pub struct Separator { 16 | spacing: f32, 17 | grow: f32, 18 | is_horizontal_line: Option, 19 | } 20 | 21 | impl Default for Separator { 22 | fn default() -> Self { 23 | Self { 24 | spacing: 6.0, 25 | grow: 0.0, 26 | is_horizontal_line: None, 27 | } 28 | } 29 | } 30 | 31 | impl Separator { 32 | /// How much space we take up. The line is painted in the middle of this. 33 | /// 34 | /// In a vertical layout, with a horizontal Separator, 35 | /// this is the height of the separator widget. 36 | /// 37 | /// In a horizontal layout, with a vertical Separator, 38 | /// this is the width of the separator widget. 39 | pub fn spacing(mut self, spacing: f32) -> Self { 40 | self.spacing = spacing; 41 | self 42 | } 43 | 44 | /// Explicitly ask for a horizontal line. 45 | /// 46 | /// By default you will get a horizontal line in vertical layouts, 47 | /// and a vertical line in horizontal layouts. 48 | pub fn horizontal(mut self) -> Self { 49 | self.is_horizontal_line = Some(true); 50 | self 51 | } 52 | 53 | /// Explicitly ask for a vertical line. 54 | /// 55 | /// By default you will get a horizontal line in vertical layouts, 56 | /// and a vertical line in horizontal layouts. 57 | pub fn vertical(mut self) -> Self { 58 | self.is_horizontal_line = Some(false); 59 | self 60 | } 61 | 62 | /// Extend each end of the separator line by this much. 63 | /// 64 | /// The default is to take up the available width/height of the parent. 65 | /// 66 | /// This will make the line extend outside the parent ui. 67 | pub fn grow(mut self, extra: f32) -> Self { 68 | self.grow += extra; 69 | self 70 | } 71 | 72 | /// Contract each end of the separator line by this much. 73 | /// 74 | /// The default is to take up the available width/height of the parent. 75 | /// 76 | /// This effectively adds margins to the line. 77 | pub fn shrink(mut self, shrink: f32) -> Self { 78 | self.grow -= shrink; 79 | self 80 | } 81 | } 82 | 83 | impl Widget for Separator { 84 | fn ui(self, ui: &mut Ui) -> Response { 85 | let Separator { 86 | spacing, 87 | grow, 88 | is_horizontal_line, 89 | } = self; 90 | 91 | let is_horizontal_line = is_horizontal_line 92 | .unwrap_or_else(|| ui.is_grid() || !ui.layout().main_dir().is_horizontal()); 93 | 94 | let available_space = ui.available_size_before_wrap(); 95 | 96 | let size = if is_horizontal_line { 97 | vec2(available_space.x, spacing) 98 | } else { 99 | vec2(spacing, available_space.y) 100 | }; 101 | 102 | let (rect, response) = ui.allocate_at_least(size, Sense::hover()); 103 | 104 | if ui.is_rect_visible(response.rect) { 105 | let stroke = ui.visuals().widgets.noninteractive.bg_stroke; 106 | let painter = ui.painter(); 107 | if is_horizontal_line { 108 | painter.hline( 109 | (rect.left() - grow)..=(rect.right() + grow), 110 | painter.round_to_pixel(rect.center().y), 111 | stroke, 112 | ); 113 | } else { 114 | painter.vline( 115 | painter.round_to_pixel(rect.center().x), 116 | (rect.top() - grow)..=(rect.bottom() + grow), 117 | stroke, 118 | ); 119 | } 120 | } 121 | 122 | response 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /crates/egui/src/widgets/spinner.rs: -------------------------------------------------------------------------------- 1 | use epaint::{emath::lerp, vec2, Color32, Pos2, Shape, Stroke}; 2 | 3 | use crate::{Response, Sense, Ui, Widget}; 4 | 5 | /// A spinner widget used to indicate loading. 6 | /// 7 | /// See also: [`crate::ProgressBar`]. 8 | #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] 9 | #[derive(Default)] 10 | pub struct Spinner { 11 | /// Uses the style's `interact_size` if `None`. 12 | size: Option, 13 | color: Option, 14 | } 15 | 16 | impl Spinner { 17 | /// Create a new spinner that uses the style's `interact_size` unless changed. 18 | pub fn new() -> Self { 19 | Self::default() 20 | } 21 | 22 | /// Sets the spinner's size. The size sets both the height and width, as the spinner is always 23 | /// square. If the size isn't set explicitly, the active style's `interact_size` is used. 24 | pub fn size(mut self, size: f32) -> Self { 25 | self.size = Some(size); 26 | self 27 | } 28 | 29 | /// Sets the spinner's color. 30 | pub fn color(mut self, color: impl Into) -> Self { 31 | self.color = Some(color.into()); 32 | self 33 | } 34 | } 35 | 36 | impl Widget for Spinner { 37 | fn ui(self, ui: &mut Ui) -> Response { 38 | let size = self 39 | .size 40 | .unwrap_or_else(|| ui.style().spacing.interact_size.y); 41 | let color = self 42 | .color 43 | .unwrap_or_else(|| ui.visuals().strong_text_color()); 44 | let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover()); 45 | 46 | if ui.is_rect_visible(rect) { 47 | ui.ctx().request_repaint(); 48 | 49 | let radius = (rect.height() / 2.0) - 2.0; 50 | let n_points = 20; 51 | let time = ui.input(|i| i.time); 52 | let start_angle = time * std::f64::consts::TAU; 53 | let end_angle = start_angle + 240f64.to_radians() * time.sin(); 54 | let points: Vec = (0..n_points) 55 | .map(|i| { 56 | let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64); 57 | let (sin, cos) = angle.sin_cos(); 58 | rect.center() + radius * vec2(cos as f32, sin as f32) 59 | }) 60 | .collect(); 61 | ui.painter() 62 | .add(Shape::line(points, Stroke::new(3.0, color))); 63 | } 64 | 65 | response 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /crates/egui/src/widgets/text_edit/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod cursor_range; 3 | mod output; 4 | mod state; 5 | mod text_buffer; 6 | 7 | pub use { 8 | builder::TextEdit, cursor_range::*, output::TextEditOutput, state::TextEditState, 9 | text_buffer::TextBuffer, 10 | }; 11 | -------------------------------------------------------------------------------- /crates/egui/src/widgets/text_edit/output.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | /// The output from a [`TextEdit`](crate::TextEdit). 4 | pub struct TextEditOutput { 5 | /// The interaction response. 6 | pub response: crate::Response, 7 | 8 | /// How the text was displayed. 9 | pub galley: Arc, 10 | 11 | /// Where the text in [`Self::galley`] ended up on the screen. 12 | pub text_draw_pos: crate::Pos2, 13 | 14 | /// The text was clipped to this rectangle when painted. 15 | pub text_clip_rect: crate::Rect, 16 | 17 | /// The state we stored after the run. 18 | pub state: super::TextEditState, 19 | 20 | /// Where the text cursor is. 21 | pub cursor_range: Option, 22 | } 23 | 24 | // TODO(emilk): add `output.paint` and `output.store` and split out that code from `TextEdit::show`. 25 | -------------------------------------------------------------------------------- /crates/egui/src/widgets/text_edit/state.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::mutex::Mutex; 4 | 5 | use crate::*; 6 | 7 | use super::{CCursorRange, CursorRange}; 8 | 9 | type Undoer = crate::util::undoer::Undoer<(CCursorRange, String)>; 10 | 11 | /// The text edit state stored between frames. 12 | #[derive(Clone, Default)] 13 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 14 | #[cfg_attr(feature = "serde", serde(default))] 15 | pub struct TextEditState { 16 | cursor_range: Option, 17 | 18 | /// This is what is easiest to work with when editing text, 19 | /// so users are more likely to read/write this. 20 | ccursor_range: Option, 21 | 22 | /// Wrapped in Arc for cheaper clones. 23 | #[cfg_attr(feature = "serde", serde(skip))] 24 | pub(crate) undoer: Arc>, 25 | 26 | // If IME candidate window is shown on this text edit. 27 | #[cfg_attr(feature = "serde", serde(skip))] 28 | pub(crate) has_ime: bool, 29 | 30 | // Visual offset when editing singleline text bigger than the width. 31 | #[cfg_attr(feature = "serde", serde(skip))] 32 | pub(crate) singleline_offset: f32, 33 | } 34 | 35 | impl TextEditState { 36 | pub fn load(ctx: &Context, id: Id) -> Option { 37 | ctx.data_mut(|d| d.get_persisted(id)) 38 | } 39 | 40 | pub fn store(self, ctx: &Context, id: Id) { 41 | ctx.data_mut(|d| d.insert_persisted(id, self)); 42 | } 43 | 44 | /// The the currently selected range of characters. 45 | pub fn ccursor_range(&self) -> Option { 46 | self.ccursor_range.or_else(|| { 47 | self.cursor_range 48 | .map(|cursor_range| cursor_range.as_ccursor_range()) 49 | }) 50 | } 51 | 52 | /// Sets the currently selected range of characters. 53 | pub fn set_ccursor_range(&mut self, ccursor_range: Option) { 54 | self.cursor_range = None; 55 | self.ccursor_range = ccursor_range; 56 | } 57 | 58 | pub fn set_cursor_range(&mut self, cursor_range: Option) { 59 | self.cursor_range = cursor_range; 60 | self.ccursor_range = None; 61 | } 62 | 63 | pub fn cursor_range(&mut self, galley: &Galley) -> Option { 64 | self.cursor_range 65 | .map(|cursor_range| { 66 | // We only use the PCursor (paragraph number, and character offset within that paragraph). 67 | // This is so that if we resize the [`TextEdit`] region, and text wrapping changes, 68 | // we keep the same byte character offset from the beginning of the text, 69 | // even though the number of rows changes 70 | // (each paragraph can be several rows, due to word wrapping). 71 | // The column (character offset) should be able to extend beyond the last word so that we can 72 | // go down and still end up on the same column when we return. 73 | CursorRange { 74 | primary: galley.from_pcursor(cursor_range.primary.pcursor), 75 | secondary: galley.from_pcursor(cursor_range.secondary.pcursor), 76 | } 77 | }) 78 | .or_else(|| { 79 | self.ccursor_range.map(|ccursor_range| CursorRange { 80 | primary: galley.from_ccursor(ccursor_range.primary), 81 | secondary: galley.from_ccursor(ccursor_range.secondary), 82 | }) 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crates/egui/src/widgets/text_edit/text_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | /// Trait constraining what types [`crate::TextEdit`] may use as 4 | /// an underlying buffer. 5 | /// 6 | /// Most likely you will use a [`String`] which implements [`TextBuffer`]. 7 | pub trait TextBuffer { 8 | /// Can this text be edited? 9 | fn is_mutable(&self) -> bool; 10 | 11 | /// Returns this buffer as a `str`. 12 | fn as_str(&self) -> &str; 13 | 14 | /// Reads the given character range. 15 | fn char_range(&self, char_range: Range) -> &str { 16 | assert!(char_range.start <= char_range.end); 17 | let start_byte = self.byte_index_from_char_index(char_range.start); 18 | let end_byte = self.byte_index_from_char_index(char_range.end); 19 | &self.as_str()[start_byte..end_byte] 20 | } 21 | 22 | fn byte_index_from_char_index(&self, char_index: usize) -> usize { 23 | byte_index_from_char_index(self.as_str(), char_index) 24 | } 25 | 26 | /// Inserts text `text` into this buffer at character index `char_index`. 27 | /// 28 | /// # Notes 29 | /// `char_index` is a *character index*, not a byte index. 30 | /// 31 | /// # Return 32 | /// Returns how many *characters* were successfully inserted 33 | fn insert_text(&mut self, text: &str, char_index: usize) -> usize; 34 | 35 | /// Deletes a range of text `char_range` from this buffer. 36 | /// 37 | /// # Notes 38 | /// `char_range` is a *character range*, not a byte range. 39 | fn delete_char_range(&mut self, char_range: Range); 40 | 41 | /// Clears all characters in this buffer 42 | fn clear(&mut self) { 43 | self.delete_char_range(0..self.as_str().len()); 44 | } 45 | 46 | /// Replaces all contents of this string with `text` 47 | fn replace(&mut self, text: &str) { 48 | self.clear(); 49 | self.insert_text(text, 0); 50 | } 51 | 52 | /// Clears all characters in this buffer and returns a string of the contents. 53 | fn take(&mut self) -> String { 54 | let s = self.as_str().to_owned(); 55 | self.clear(); 56 | s 57 | } 58 | } 59 | 60 | impl TextBuffer for String { 61 | fn is_mutable(&self) -> bool { 62 | true 63 | } 64 | 65 | fn as_str(&self) -> &str { 66 | self.as_ref() 67 | } 68 | 69 | fn insert_text(&mut self, text: &str, char_index: usize) -> usize { 70 | // Get the byte index from the character index 71 | let byte_idx = self.byte_index_from_char_index(char_index); 72 | 73 | // Then insert the string 74 | self.insert_str(byte_idx, text); 75 | 76 | text.chars().count() 77 | } 78 | 79 | fn delete_char_range(&mut self, char_range: Range) { 80 | assert!(char_range.start <= char_range.end); 81 | 82 | // Get both byte indices 83 | let byte_start = self.byte_index_from_char_index(char_range.start); 84 | let byte_end = self.byte_index_from_char_index(char_range.end); 85 | 86 | // Then drain all characters within this range 87 | self.drain(byte_start..byte_end); 88 | } 89 | 90 | fn clear(&mut self) { 91 | self.clear(); 92 | } 93 | 94 | fn replace(&mut self, text: &str) { 95 | *self = text.to_owned(); 96 | } 97 | 98 | fn take(&mut self) -> String { 99 | std::mem::take(self) 100 | } 101 | } 102 | 103 | /// Immutable view of a `&str`! 104 | impl<'a> TextBuffer for &'a str { 105 | fn is_mutable(&self) -> bool { 106 | false 107 | } 108 | 109 | fn as_str(&self) -> &str { 110 | self 111 | } 112 | 113 | fn insert_text(&mut self, _text: &str, _ch_idx: usize) -> usize { 114 | 0 115 | } 116 | 117 | fn delete_char_range(&mut self, _ch_range: Range) {} 118 | } 119 | 120 | fn byte_index_from_char_index(s: &str, char_index: usize) -> usize { 121 | for (ci, (bi, _)) in s.char_indices().enumerate() { 122 | if ci == char_index { 123 | return bi; 124 | } 125 | } 126 | s.len() 127 | } 128 | -------------------------------------------------------------------------------- /crates/egui_demo_app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_demo_app" 3 | version = "0.20.0" 4 | authors = ["Emil Ernerfeldt "] 5 | license = "MIT OR Apache-2.0" 6 | edition = "2021" 7 | rust-version = "1.65" 8 | publish = false 9 | default-run = "egui_demo_app" 10 | 11 | [package.metadata.docs.rs] 12 | all-features = true 13 | 14 | [lib] 15 | crate-type = ["cdylib", "rlib"] 16 | 17 | 18 | [features] 19 | default = ["glow", "persistence"] 20 | 21 | http = ["ehttp", "image", "poll-promise", "egui_extras/image"] 22 | persistence = ["eframe/persistence", "egui/persistence", "serde"] 23 | web_screen_reader = ["eframe/web_screen_reader"] # experimental 24 | serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"] 25 | syntax_highlighting = ["egui_demo_lib/syntax_highlighting"] 26 | 27 | glow = ["eframe/glow"] 28 | wgpu = ["eframe/wgpu", "bytemuck"] 29 | 30 | 31 | [dependencies] 32 | chrono = { version = "0.4", features = ["js-sys", "wasmbind"] } 33 | eframe = { version = "0.20.0", path = "../eframe", default-features = false } 34 | egui = { version = "0.20.0", path = "../egui", features = [ 35 | "extra_debug_asserts", 36 | ] } 37 | egui_demo_lib = { version = "0.20.0", path = "../egui_demo_lib", features = [ 38 | "chrono", 39 | ] } 40 | tracing = "0.1" 41 | 42 | # Optional dependencies: 43 | 44 | bytemuck = { version = "1.7.1", optional = true } 45 | egui_extras = { version = "0.20.0", optional = true, path = "../egui_extras" } 46 | 47 | # feature "http": 48 | ehttp = { version = "0.2.0", optional = true } 49 | image = { version = "0.24", optional = true, default-features = false, features = [ 50 | "jpeg", 51 | "png", 52 | ] } 53 | poll-promise = { version = "0.2", optional = true, default-features = false } 54 | 55 | # feature "persistence": 56 | serde = { version = "1", optional = true, features = ["derive"] } 57 | 58 | 59 | # native: 60 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 61 | tracing-subscriber = "0.3" 62 | 63 | # web: 64 | [target.'cfg(target_arch = "wasm32")'.dependencies] 65 | console_error_panic_hook = "0.1.6" 66 | tracing-wasm = "0.2" 67 | wasm-bindgen-futures = "0.4" 68 | -------------------------------------------------------------------------------- /crates/egui_demo_app/README.md: -------------------------------------------------------------------------------- 1 | # egui demo app 2 | This app demonstrates [`egui`](https://github.com/emilk/egui/) and [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe). 3 | 4 | View the demo app online at . 5 | 6 | Run it locally with `cargo run --release -p egui_demo_app`. 7 | 8 | `egui_demo_app` can be compiled to WASM and viewed in a browser locally with: 9 | 10 | ```sh 11 | ./sh/start_server.sh & 12 | ./sh/build_demo_web.sh --open 13 | ``` 14 | 15 | `egui_demo_app` uses [`egui_demo_lib`](https://github.com/emilk/egui/tree/master/crates/egui_demo_lib). 16 | 17 | 18 | ## Running with `wgpu` backend 19 | `(cd egui_demo_app && cargo r --features wgpu)` 20 | -------------------------------------------------------------------------------- /crates/egui_demo_app/src/apps/custom3d_wgpu_shader.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOut { 2 | @location(0) color: vec4, 3 | @builtin(position) position: vec4, 4 | }; 5 | 6 | struct Uniforms { 7 | @size(16) angle: f32, // pad to 16 bytes 8 | }; 9 | 10 | @group(0) @binding(0) 11 | var uniforms: Uniforms; 12 | 13 | var v_positions: array, 3> = array, 3>( 14 | vec2(0.0, 1.0), 15 | vec2(1.0, -1.0), 16 | vec2(-1.0, -1.0), 17 | ); 18 | 19 | var v_colors: array, 3> = array, 3>( 20 | vec4(1.0, 0.0, 0.0, 1.0), 21 | vec4(0.0, 1.0, 0.0, 1.0), 22 | vec4(0.0, 0.0, 1.0, 1.0), 23 | ); 24 | 25 | @vertex 26 | fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut { 27 | var out: VertexOut; 28 | 29 | out.position = vec4(v_positions[v_idx], 0.0, 1.0); 30 | out.position.x = out.position.x * cos(uniforms.angle); 31 | out.color = v_colors[v_idx]; 32 | 33 | return out; 34 | } 35 | 36 | @fragment 37 | fn fs_main(in: VertexOut) -> @location(0) vec4 { 38 | return in.color; 39 | } 40 | -------------------------------------------------------------------------------- /crates/egui_demo_app/src/apps/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(feature = "glow", not(feature = "wgpu")))] 2 | mod custom3d_glow; 3 | 4 | #[cfg(feature = "wgpu")] 5 | mod custom3d_wgpu; 6 | 7 | mod fractal_clock; 8 | 9 | #[cfg(feature = "http")] 10 | mod http_app; 11 | 12 | #[cfg(all(feature = "glow", not(feature = "wgpu")))] 13 | pub use custom3d_glow::Custom3d; 14 | 15 | #[cfg(feature = "wgpu")] 16 | pub use custom3d_wgpu::Custom3d; 17 | 18 | pub use fractal_clock::FractalClock; 19 | 20 | #[cfg(feature = "http")] 21 | pub use http_app::HttpApp; 22 | -------------------------------------------------------------------------------- /crates/egui_demo_app/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Demo app for egui 2 | #![allow(clippy::missing_errors_doc)] 3 | 4 | mod apps; 5 | mod backend_panel; 6 | pub(crate) mod frame_history; 7 | mod wrap_app; 8 | 9 | #[cfg(target_arch = "wasm32")] 10 | use eframe::web::AppRunnerRef; 11 | 12 | pub use wrap_app::WrapApp; 13 | 14 | /// Time of day as seconds since midnight. Used for clock in demo app. 15 | pub(crate) fn seconds_since_midnight() -> f64 { 16 | use chrono::Timelike; 17 | let time = chrono::Local::now().time(); 18 | time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64) 19 | } 20 | 21 | // ---------------------------------------------------------------------------- 22 | 23 | #[cfg(target_arch = "wasm32")] 24 | use eframe::wasm_bindgen::{self, prelude::*}; 25 | 26 | #[cfg(target_arch = "wasm32")] 27 | #[wasm_bindgen] 28 | pub struct WebHandle { 29 | handle: AppRunnerRef, 30 | } 31 | 32 | #[cfg(target_arch = "wasm32")] 33 | #[wasm_bindgen] 34 | impl WebHandle { 35 | #[wasm_bindgen] 36 | pub fn stop_web(&self) -> Result<(), wasm_bindgen::JsValue> { 37 | let mut app = self.handle.lock(); 38 | app.destroy() 39 | } 40 | 41 | #[wasm_bindgen] 42 | pub fn set_some_content_from_javasript(&mut self, _some_data: &str) { 43 | let _app = self.handle.lock().app_mut::(); 44 | // _app.data = some_data; 45 | } 46 | } 47 | 48 | #[cfg(target_arch = "wasm32")] 49 | #[wasm_bindgen] 50 | pub fn init_wasm_hooks() { 51 | // Make sure panics are logged using `console.error`. 52 | console_error_panic_hook::set_once(); 53 | 54 | // Redirect tracing to console.log and friends: 55 | tracing_wasm::set_as_global_default(); 56 | } 57 | 58 | #[cfg(target_arch = "wasm32")] 59 | #[wasm_bindgen] 60 | pub async fn start_separate(canvas_id: &str) -> Result { 61 | let web_options = eframe::WebOptions::default(); 62 | eframe::start_web( 63 | canvas_id, 64 | web_options, 65 | Box::new(|cc| Box::new(WrapApp::new(cc))), 66 | ) 67 | .await 68 | .map(|handle| WebHandle { handle }) 69 | } 70 | 71 | /// This is the entry-point for all the web-assembly. 72 | /// This is called once from the HTML. 73 | /// It loads the app, installs some callbacks, then returns. 74 | /// You can add more callbacks like this if you want to call in to your code. 75 | #[cfg(target_arch = "wasm32")] 76 | #[wasm_bindgen] 77 | pub async fn start(canvas_id: &str) -> Result { 78 | init_wasm_hooks(); 79 | start_separate(canvas_id).await 80 | } 81 | -------------------------------------------------------------------------------- /crates/egui_demo_app/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Demo app for egui 2 | 3 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release 4 | 5 | // When compiling natively: 6 | fn main() -> Result<(), eframe::Error> { 7 | { 8 | // Silence wgpu log spam (https://github.com/gfx-rs/wgpu/issues/3206) 9 | let mut rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned()); 10 | for loud_crate in ["naga", "wgpu_core", "wgpu_hal"] { 11 | if !rust_log.contains(&format!("{loud_crate}=")) { 12 | rust_log += &format!(",{loud_crate}=warn"); 13 | } 14 | } 15 | std::env::set_var("RUST_LOG", rust_log); 16 | } 17 | 18 | // Log to stdout (if you run with `RUST_LOG=debug`). 19 | tracing_subscriber::fmt::init(); 20 | 21 | let options = eframe::NativeOptions { 22 | drag_and_drop_support: true, 23 | 24 | initial_window_size: Some([1280.0, 1024.0].into()), 25 | 26 | #[cfg(feature = "wgpu")] 27 | renderer: eframe::Renderer::Wgpu, 28 | 29 | ..Default::default() 30 | }; 31 | eframe::run_native( 32 | "egui demo app", 33 | options, 34 | Box::new(|cc| Box::new(egui_demo_app::WrapApp::new(cc))), 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_demo_lib" 3 | version = "0.20.0" 4 | authors = ["Emil Ernerfeldt "] 5 | description = "Example library for egui" 6 | edition = "2021" 7 | rust-version = "1.65" 8 | homepage = "https://github.com/emilk/egui/tree/master/crates/egui_demo_lib" 9 | license = "MIT OR Apache-2.0" 10 | readme = "README.md" 11 | repository = "https://github.com/emilk/egui/tree/master/crates/egui_demo_lib" 12 | categories = ["gui", "graphics"] 13 | keywords = ["glium", "egui", "gui", "gamedev"] 14 | include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] 15 | 16 | [package.metadata.docs.rs] 17 | all-features = true 18 | 19 | [lib] 20 | 21 | 22 | [features] 23 | default = [] 24 | 25 | chrono = ["egui_extras/datepicker", "dep:chrono"] 26 | ## Allow serialization using [`serde`](https://docs.rs/serde). 27 | serde = ["egui/serde", "dep:serde"] 28 | ## Enable better syntax highlighting using [`syntect`](https://docs.rs/syntect). 29 | syntax_highlighting = ["syntect"] 30 | 31 | 32 | [dependencies] 33 | egui = { version = "0.20.0", path = "../egui", default-features = false } 34 | egui_extras = { version = "0.20.0", path = "../egui_extras" } 35 | enum-map = { version = "2", features = ["serde"] } 36 | tracing = { version = "0.1", default-features = false, features = ["std"] } 37 | unicode_names2 = { version = "0.6.0", default-features = false } 38 | 39 | #! ### Optional dependencies 40 | chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] } 41 | ## Enable this when generating docs. 42 | document-features = { version = "0.2", optional = true } 43 | serde = { version = "1", optional = true, features = ["derive"] } 44 | syntect = { version = "5", optional = true, default-features = false, features = [ 45 | "default-fancy", 46 | ] } 47 | 48 | 49 | [dev-dependencies] 50 | criterion = { version = "0.4", default-features = false } 51 | 52 | 53 | [[bench]] 54 | name = "benchmark" 55 | harness = false 56 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/README.md: -------------------------------------------------------------------------------- 1 | # [`egui`](https://github.com/emilk/egui) demo library 2 | 3 | [![Latest version](https://img.shields.io/crates/v/egui_demo_lib.svg)](https://crates.io/crates/egui_demo_lib) 4 | [![Documentation](https://docs.rs/egui_demo_lib/badge.svg)](https://docs.rs/egui_demo_lib) 5 | [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) 6 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 7 | ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) 8 | 9 | This crate contains example code for [`egui`](https://github.com/emilk/egui). 10 | 11 | The demo library is a separate crate for three reasons: 12 | 13 | * To ensure it only uses the public `egui` api. 14 | * To remove the amount of code in `egui` proper. 15 | * To make it easy for 3rd party egui integrations to use it for tests. 16 | - See for instance https://github.com/not-fl3/egui-miniquad/blob/master/examples/demo.rs 17 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/demo/about.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 3 | #[cfg_attr(feature = "serde", serde(default))] 4 | pub struct About {} 5 | 6 | impl super::Demo for About { 7 | fn name(&self) -> &'static str { 8 | "About egui" 9 | } 10 | 11 | fn show(&mut self, ctx: &egui::Context, open: &mut bool) { 12 | egui::Window::new(self.name()) 13 | .default_width(320.0) 14 | .open(open) 15 | .show(ctx, |ui| { 16 | use super::View as _; 17 | self.ui(ui); 18 | }); 19 | } 20 | } 21 | 22 | impl super::View for About { 23 | fn ui(&mut self, ui: &mut egui::Ui) { 24 | use egui::special_emojis::{OS_APPLE, OS_LINUX, OS_WINDOWS}; 25 | 26 | ui.heading("egui"); 27 | ui.label(format!( 28 | "egui is an immediate mode GUI library written in Rust. egui runs both on the web and natively on {}{}{}. \ 29 | On the web it is compiled to WebAssembly and rendered with WebGL.{}", 30 | OS_APPLE, OS_LINUX, OS_WINDOWS, 31 | if cfg!(target_arch = "wasm32") { 32 | " Everything you see is rendered as textured triangles. There is no DOM, HTML, JS or CSS. Just Rust." 33 | } else {""} 34 | )); 35 | ui.label("egui is designed to be easy to use, portable, and fast."); 36 | 37 | ui.add_space(12.0); // ui.separator(); 38 | ui.heading("Immediate mode"); 39 | about_immediate_mode(ui); 40 | 41 | ui.add_space(12.0); // ui.separator(); 42 | ui.heading("Links"); 43 | links(ui); 44 | } 45 | } 46 | 47 | fn about_immediate_mode(ui: &mut egui::Ui) { 48 | use crate::syntax_highlighting::code_view_ui; 49 | ui.style_mut().spacing.interact_size.y = 0.0; // hack to make `horizontal_wrapped` work better with text. 50 | 51 | ui.horizontal_wrapped(|ui| { 52 | ui.spacing_mut().item_spacing.x = 0.0; 53 | ui.label("Immediate mode is a GUI paradigm that lets you create a GUI with less code and simpler control flow. For example, this is how you create a "); 54 | let _ = ui.small_button("button"); 55 | ui.label(" in egui:"); 56 | }); 57 | 58 | ui.add_space(8.0); 59 | code_view_ui( 60 | ui, 61 | r#" 62 | if ui.button("Save").clicked() { 63 | my_state.save(); 64 | }"# 65 | .trim_start_matches('\n'), 66 | ); 67 | ui.add_space(8.0); 68 | 69 | ui.label("Note how there are no callbacks or messages, and no button state to store."); 70 | 71 | ui.label("Immediate mode has its roots in gaming, where everything on the screen is painted at the display refresh rate, i.e. at 60+ frames per second. \ 72 | In immediate mode GUIs, the entire interface is layed out and painted at the same high rate. \ 73 | This makes immediate mode GUIs especially well suited for highly interactive applications."); 74 | 75 | ui.horizontal_wrapped(|ui| { 76 | ui.spacing_mut().item_spacing.x = 0.0; 77 | ui.label("More about immediate mode "); 78 | ui.hyperlink_to("here", "https://github.com/emilk/egui#why-immediate-mode"); 79 | ui.label("."); 80 | }); 81 | } 82 | 83 | fn links(ui: &mut egui::Ui) { 84 | use egui::special_emojis::{GITHUB, TWITTER}; 85 | ui.hyperlink_to( 86 | format!("{} egui on GitHub", GITHUB), 87 | "https://github.com/emilk/egui", 88 | ); 89 | ui.hyperlink_to( 90 | format!("{} @ernerfeldt", TWITTER), 91 | "https://twitter.com/ernerfeldt", 92 | ); 93 | ui.hyperlink_to("egui documentation", "https://docs.rs/egui/"); 94 | } 95 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/demo/code_editor.rs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | 3 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 4 | #[cfg_attr(feature = "serde", serde(default))] 5 | pub struct CodeEditor { 6 | language: String, 7 | code: String, 8 | } 9 | 10 | impl Default for CodeEditor { 11 | fn default() -> Self { 12 | Self { 13 | language: "rs".into(), 14 | code: "// A very simple example\n\ 15 | fn main() {\n\ 16 | \tprintln!(\"Hello world!\");\n\ 17 | }\n\ 18 | " 19 | .into(), 20 | } 21 | } 22 | } 23 | 24 | impl super::Demo for CodeEditor { 25 | fn name(&self) -> &'static str { 26 | "🖮 Code Editor" 27 | } 28 | 29 | fn show(&mut self, ctx: &egui::Context, open: &mut bool) { 30 | use super::View as _; 31 | egui::Window::new(self.name()) 32 | .open(open) 33 | .default_height(500.0) 34 | .show(ctx, |ui| self.ui(ui)); 35 | } 36 | } 37 | 38 | impl super::View for CodeEditor { 39 | fn ui(&mut self, ui: &mut egui::Ui) { 40 | let Self { language, code } = self; 41 | 42 | ui.horizontal(|ui| { 43 | ui.set_height(0.0); 44 | ui.label("An example of syntax highlighting in a TextEdit."); 45 | ui.add(crate::egui_github_link_file!()); 46 | }); 47 | 48 | if cfg!(feature = "syntect") { 49 | ui.horizontal(|ui| { 50 | ui.label("Language:"); 51 | ui.text_edit_singleline(language); 52 | }); 53 | ui.horizontal_wrapped(|ui| { 54 | ui.spacing_mut().item_spacing.x = 0.0; 55 | ui.label("Syntax highlighting powered by "); 56 | ui.hyperlink_to("syntect", "https://github.com/trishume/syntect"); 57 | ui.label("."); 58 | }); 59 | } else { 60 | ui.horizontal_wrapped(|ui| { 61 | ui.spacing_mut().item_spacing.x = 0.0; 62 | ui.label("Compile the demo with the "); 63 | ui.code("syntax_highlighting"); 64 | ui.label(" feature to enable more accurate syntax highlighting using "); 65 | ui.hyperlink_to("syntect", "https://github.com/trishume/syntect"); 66 | ui.label("."); 67 | }); 68 | } 69 | 70 | let mut theme = crate::syntax_highlighting::CodeTheme::from_memory(ui.ctx()); 71 | ui.collapsing("Theme", |ui| { 72 | ui.group(|ui| { 73 | theme.ui(ui); 74 | theme.clone().store_in_memory(ui.ctx()); 75 | }); 76 | }); 77 | 78 | let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| { 79 | let mut layout_job = 80 | crate::syntax_highlighting::highlight(ui.ctx(), &theme, string, language); 81 | layout_job.wrap.max_width = wrap_width; 82 | ui.fonts(|f| f.layout_job(layout_job)) 83 | }; 84 | 85 | egui::ScrollArea::vertical().show(ui, |ui| { 86 | ui.add( 87 | egui::TextEdit::multiline(code) 88 | .font(egui::TextStyle::Monospace) // for cursor height 89 | .code_editor() 90 | .desired_rows(10) 91 | .lock_focus(true) 92 | .desired_width(f32::INFINITY) 93 | .layouter(&mut layouter), 94 | ); 95 | }); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/demo/dancing_strings.rs: -------------------------------------------------------------------------------- 1 | use egui::{containers::*, *}; 2 | 3 | #[derive(Default)] 4 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 5 | #[cfg_attr(feature = "serde", serde(default))] 6 | pub struct DancingStrings {} 7 | 8 | impl super::Demo for DancingStrings { 9 | fn name(&self) -> &'static str { 10 | "♫ Dancing Strings" 11 | } 12 | 13 | fn show(&mut self, ctx: &Context, open: &mut bool) { 14 | use super::View as _; 15 | Window::new(self.name()) 16 | .open(open) 17 | .default_size(vec2(512.0, 256.0)) 18 | .vscroll(false) 19 | .show(ctx, |ui| self.ui(ui)); 20 | } 21 | } 22 | 23 | impl super::View for DancingStrings { 24 | fn ui(&mut self, ui: &mut Ui) { 25 | let color = if ui.visuals().dark_mode { 26 | Color32::from_additive_luminance(196) 27 | } else { 28 | Color32::from_black_alpha(240) 29 | }; 30 | 31 | Frame::canvas(ui.style()).show(ui, |ui| { 32 | ui.ctx().request_repaint(); 33 | let time = ui.input(|i| i.time); 34 | 35 | let desired_size = ui.available_width() * vec2(1.0, 0.35); 36 | let (_id, rect) = ui.allocate_space(desired_size); 37 | 38 | let to_screen = 39 | emath::RectTransform::from_to(Rect::from_x_y_ranges(0.0..=1.0, -1.0..=1.0), rect); 40 | 41 | let mut shapes = vec![]; 42 | 43 | for &mode in &[2, 3, 5] { 44 | let mode = mode as f64; 45 | let n = 120; 46 | let speed = 1.5; 47 | 48 | let points: Vec = (0..=n) 49 | .map(|i| { 50 | let t = i as f64 / (n as f64); 51 | let amp = (time * speed * mode).sin() / mode; 52 | let y = amp * (t * std::f64::consts::TAU / 2.0 * mode).sin(); 53 | to_screen * pos2(t as f32, y as f32) 54 | }) 55 | .collect(); 56 | 57 | let thickness = 10.0 / mode as f32; 58 | shapes.push(epaint::Shape::line(points, Stroke::new(thickness, color))); 59 | } 60 | 61 | ui.painter().extend(shapes); 62 | }); 63 | ui.vertical_centered(|ui| { 64 | ui.add(crate::egui_github_link_file!()); 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/demo/highlighting.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 3 | #[cfg_attr(feature = "serde", serde(default))] 4 | pub struct Highlighting {} 5 | 6 | impl super::Demo for Highlighting { 7 | fn name(&self) -> &'static str { 8 | "Highlighting" 9 | } 10 | 11 | fn show(&mut self, ctx: &egui::Context, open: &mut bool) { 12 | egui::Window::new(self.name()) 13 | .default_width(320.0) 14 | .open(open) 15 | .show(ctx, |ui| { 16 | use super::View as _; 17 | self.ui(ui); 18 | }); 19 | } 20 | } 21 | 22 | impl super::View for Highlighting { 23 | fn ui(&mut self, ui: &mut egui::Ui) { 24 | ui.label("This demo demonstrates highlighting a widget."); 25 | ui.add_space(4.0); 26 | let label_response = ui.label("Hover me to highlight the button!"); 27 | ui.add_space(4.0); 28 | let mut button_response = ui.button("Hover the button to highlight the label!"); 29 | 30 | if label_response.hovered() { 31 | button_response = button_response.highlight(); 32 | } 33 | if button_response.hovered() { 34 | label_response.highlight(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/demo/mod.rs: -------------------------------------------------------------------------------- 1 | //! Demo-code for showing how egui is used. 2 | //! 3 | //! The demo-code is also used in benchmarks and tests. 4 | 5 | // ---------------------------------------------------------------------------- 6 | 7 | pub mod about; 8 | pub mod code_editor; 9 | pub mod code_example; 10 | pub mod context_menu; 11 | pub mod dancing_strings; 12 | pub mod demo_app_windows; 13 | pub mod drag_and_drop; 14 | pub mod font_book; 15 | pub mod highlighting; 16 | pub mod layout_test; 17 | pub mod misc_demo_window; 18 | pub mod multi_touch; 19 | pub mod paint_bezier; 20 | pub mod painting; 21 | pub mod password; 22 | pub mod plot_demo; 23 | pub mod scrolling; 24 | pub mod sliders; 25 | pub mod strip_demo; 26 | pub mod table_demo; 27 | pub mod tests; 28 | pub mod text_edit; 29 | pub mod toggle_switch; 30 | pub mod widget_gallery; 31 | pub mod window_options; 32 | pub mod window_with_panels; 33 | 34 | pub use { 35 | about::About, demo_app_windows::DemoWindows, misc_demo_window::MiscDemoWindow, 36 | widget_gallery::WidgetGallery, 37 | }; 38 | 39 | // ---------------------------------------------------------------------------- 40 | 41 | /// Something to view in the demo windows 42 | pub trait View { 43 | fn ui(&mut self, ui: &mut egui::Ui); 44 | } 45 | 46 | /// Something to view 47 | pub trait Demo { 48 | /// `&'static` so we can also use it as a key to store open/close state. 49 | fn name(&self) -> &'static str; 50 | 51 | /// Show windows, etc 52 | fn show(&mut self, ctx: &egui::Context, open: &mut bool); 53 | } 54 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/demo/painting.rs: -------------------------------------------------------------------------------- 1 | use egui::*; 2 | 3 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 4 | #[cfg_attr(feature = "serde", serde(default))] 5 | pub struct Painting { 6 | /// in 0-1 normalized coordinates 7 | lines: Vec>, 8 | stroke: Stroke, 9 | } 10 | 11 | impl Default for Painting { 12 | fn default() -> Self { 13 | Self { 14 | lines: Default::default(), 15 | stroke: Stroke::new(1.0, Color32::from_rgb(25, 200, 100)), 16 | } 17 | } 18 | } 19 | 20 | impl Painting { 21 | pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response { 22 | ui.horizontal(|ui| { 23 | egui::stroke_ui(ui, &mut self.stroke, "Stroke"); 24 | ui.separator(); 25 | if ui.button("Clear Painting").clicked() { 26 | self.lines.clear(); 27 | } 28 | }) 29 | .response 30 | } 31 | 32 | pub fn ui_content(&mut self, ui: &mut Ui) -> egui::Response { 33 | let (mut response, painter) = 34 | ui.allocate_painter(ui.available_size_before_wrap(), Sense::drag()); 35 | 36 | let to_screen = emath::RectTransform::from_to( 37 | Rect::from_min_size(Pos2::ZERO, response.rect.square_proportions()), 38 | response.rect, 39 | ); 40 | let from_screen = to_screen.inverse(); 41 | 42 | if self.lines.is_empty() { 43 | self.lines.push(vec![]); 44 | } 45 | 46 | let current_line = self.lines.last_mut().unwrap(); 47 | 48 | if let Some(pointer_pos) = response.interact_pointer_pos() { 49 | let canvas_pos = from_screen * pointer_pos; 50 | if current_line.last() != Some(&canvas_pos) { 51 | current_line.push(canvas_pos); 52 | response.mark_changed(); 53 | } 54 | } else if !current_line.is_empty() { 55 | self.lines.push(vec![]); 56 | response.mark_changed(); 57 | } 58 | 59 | let shapes = self 60 | .lines 61 | .iter() 62 | .filter(|line| line.len() >= 2) 63 | .map(|line| { 64 | let points: Vec = line.iter().map(|p| to_screen * *p).collect(); 65 | egui::Shape::line(points, self.stroke) 66 | }); 67 | 68 | painter.extend(shapes); 69 | 70 | response 71 | } 72 | } 73 | 74 | impl super::Demo for Painting { 75 | fn name(&self) -> &'static str { 76 | "🖊 Painting" 77 | } 78 | 79 | fn show(&mut self, ctx: &Context, open: &mut bool) { 80 | use super::View as _; 81 | Window::new(self.name()) 82 | .open(open) 83 | .default_size(vec2(512.0, 512.0)) 84 | .vscroll(false) 85 | .show(ctx, |ui| self.ui(ui)); 86 | } 87 | } 88 | 89 | impl super::View for Painting { 90 | fn ui(&mut self, ui: &mut Ui) { 91 | ui.vertical_centered(|ui| { 92 | ui.add(crate::egui_github_link_file!()); 93 | }); 94 | self.ui_control(ui); 95 | ui.label("Paint with your mouse/touch!"); 96 | Frame::canvas(ui.style()).show(ui, |ui| { 97 | self.ui_content(ui); 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/demo/password.rs: -------------------------------------------------------------------------------- 1 | //! Source code example about creating a widget which uses `egui::Memory` to store UI state. 2 | //! 3 | //! This is meant to be read as a tutorial, hence the plethora of comments. 4 | 5 | /// Password entry field with ability to toggle character hiding. 6 | /// 7 | /// ## Example: 8 | /// ``` ignore 9 | /// password_ui(ui, &mut my_password); 10 | /// ``` 11 | #[allow(clippy::ptr_arg)] // false positive 12 | pub fn password_ui(ui: &mut egui::Ui, password: &mut String) -> egui::Response { 13 | // This widget has its own state — show or hide password characters (`show_plaintext`). 14 | // In this case we use a simple `bool`, but you can also declare your own type. 15 | // It must implement at least `Clone` and be `'static`. 16 | // If you use the `persistence` feature, it also must implement `serde::{Deserialize, Serialize}`. 17 | 18 | // Generate an id for the state 19 | let state_id = ui.id().with("show_plaintext"); 20 | 21 | // Get state for this widget. 22 | // You should get state by value, not by reference to avoid borrowing of [`Memory`]. 23 | let mut show_plaintext = ui.data_mut(|d| d.get_temp::(state_id).unwrap_or(false)); 24 | 25 | // Process ui, change a local copy of the state 26 | // We want TextEdit to fill entire space, and have button after that, so in that case we can 27 | // change direction to right_to_left. 28 | let result = ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { 29 | // Toggle the `show_plaintext` bool with a button: 30 | let response = ui 31 | .add(egui::SelectableLabel::new(show_plaintext, "👁")) 32 | .on_hover_text("Show/hide password"); 33 | 34 | if response.clicked() { 35 | show_plaintext = !show_plaintext; 36 | } 37 | 38 | // Show the password field: 39 | ui.add_sized( 40 | ui.available_size(), 41 | egui::TextEdit::singleline(password).password(!show_plaintext), 42 | ); 43 | }); 44 | 45 | // Store the (possibly changed) state: 46 | ui.data_mut(|d| d.insert_temp(state_id, show_plaintext)); 47 | 48 | // All done! Return the interaction response so the user can check what happened 49 | // (hovered, clicked, …) and maybe show a tooltip: 50 | result.response 51 | } 52 | 53 | // A wrapper that allows the more idiomatic usage pattern: `ui.add(…)` 54 | /// Password entry field with ability to toggle character hiding. 55 | /// 56 | /// ## Example: 57 | /// ``` ignore 58 | /// ui.add(password(&mut my_password)); 59 | /// ``` 60 | pub fn password(password: &mut String) -> impl egui::Widget + '_ { 61 | move |ui: &mut egui::Ui| password_ui(ui, password) 62 | } 63 | 64 | pub fn url_to_file_source_code() -> String { 65 | format!("https://github.com/emilk/egui/blob/master/{}", file!()) 66 | } 67 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/demo/text_edit.rs: -------------------------------------------------------------------------------- 1 | /// Showcase [`TextEdit`]. 2 | #[derive(PartialEq, Eq)] 3 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 4 | #[cfg_attr(feature = "serde", serde(default))] 5 | pub struct TextEdit { 6 | pub text: String, 7 | } 8 | 9 | impl Default for TextEdit { 10 | fn default() -> Self { 11 | Self { 12 | text: "Edit this text".to_owned(), 13 | } 14 | } 15 | } 16 | 17 | impl super::Demo for TextEdit { 18 | fn name(&self) -> &'static str { 19 | "🖹 TextEdit" 20 | } 21 | 22 | fn show(&mut self, ctx: &egui::Context, open: &mut bool) { 23 | egui::Window::new(self.name()) 24 | .open(open) 25 | .resizable(false) 26 | .show(ctx, |ui| { 27 | use super::View as _; 28 | self.ui(ui); 29 | }); 30 | } 31 | } 32 | 33 | impl super::View for TextEdit { 34 | fn ui(&mut self, ui: &mut egui::Ui) { 35 | let Self { text } = self; 36 | 37 | ui.horizontal(|ui| { 38 | ui.spacing_mut().item_spacing.x = 0.0; 39 | ui.label("Advanced usage of "); 40 | ui.code("TextEdit"); 41 | ui.label("."); 42 | }); 43 | 44 | let output = egui::TextEdit::multiline(text) 45 | .hint_text("Type something!") 46 | .show(ui); 47 | 48 | ui.horizontal(|ui| { 49 | ui.spacing_mut().item_spacing.x = 0.0; 50 | ui.label("Selected text: "); 51 | if let Some(text_cursor_range) = output.cursor_range { 52 | use egui::TextBuffer as _; 53 | let selected_chars = text_cursor_range.as_sorted_char_range(); 54 | let selected_text = text.char_range(selected_chars); 55 | ui.code(selected_text); 56 | } 57 | }); 58 | 59 | let anything_selected = output 60 | .cursor_range 61 | .map_or(false, |cursor| !cursor.is_empty()); 62 | 63 | ui.add_enabled( 64 | anything_selected, 65 | egui::Label::new("Press ctrl+Y to toggle the case of selected text (cmd+Y on Mac)"), 66 | ); 67 | 68 | if ui.input_mut(|i| i.consume_key(egui::Modifiers::COMMAND, egui::Key::Y)) { 69 | if let Some(text_cursor_range) = output.cursor_range { 70 | use egui::TextBuffer as _; 71 | let selected_chars = text_cursor_range.as_sorted_char_range(); 72 | let selected_text = text.char_range(selected_chars.clone()); 73 | let upper_case = selected_text.to_uppercase(); 74 | let new_text = if selected_text == upper_case { 75 | selected_text.to_lowercase() 76 | } else { 77 | upper_case 78 | }; 79 | text.delete_char_range(selected_chars.clone()); 80 | text.insert_text(&new_text, selected_chars.start); 81 | } 82 | } 83 | 84 | ui.horizontal(|ui| { 85 | ui.label("Move cursor to the:"); 86 | 87 | if ui.button("start").clicked() { 88 | let text_edit_id = output.response.id; 89 | if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) { 90 | let ccursor = egui::text::CCursor::new(0); 91 | state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor))); 92 | state.store(ui.ctx(), text_edit_id); 93 | ui.ctx().memory_mut(|mem| mem.request_focus(text_edit_id)); // give focus back to the [`TextEdit`]. 94 | } 95 | } 96 | 97 | if ui.button("end").clicked() { 98 | let text_edit_id = output.response.id; 99 | if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) { 100 | let ccursor = egui::text::CCursor::new(text.chars().count()); 101 | state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor))); 102 | state.store(ui.ctx(), text_edit_id); 103 | ui.ctx().memory_mut(|mem| mem.request_focus(text_edit_id)); // give focus back to the [`TextEdit`]. 104 | } 105 | } 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/demo/window_with_panels.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Default, PartialEq, Eq)] 2 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 3 | pub struct WindowWithPanels {} 4 | 5 | impl super::Demo for WindowWithPanels { 6 | fn name(&self) -> &'static str { 7 | "🗖 Window With Panels" 8 | } 9 | 10 | fn show(&mut self, ctx: &egui::Context, open: &mut bool) { 11 | use super::View as _; 12 | let window = egui::Window::new("Window with Panels") 13 | .default_width(600.0) 14 | .default_height(400.0) 15 | .vscroll(false) 16 | .open(open); 17 | window.show(ctx, |ui| self.ui(ui)); 18 | } 19 | } 20 | 21 | impl super::View for WindowWithPanels { 22 | fn ui(&mut self, ui: &mut egui::Ui) { 23 | // Note that the order we add the panels is very important! 24 | 25 | egui::TopBottomPanel::top("top_panel") 26 | .resizable(true) 27 | .min_height(32.0) 28 | .show_inside(ui, |ui| { 29 | egui::ScrollArea::vertical().show(ui, |ui| { 30 | ui.vertical_centered(|ui| { 31 | ui.heading("Expandable Upper Panel"); 32 | }); 33 | lorem_ipsum(ui); 34 | }); 35 | }); 36 | 37 | egui::SidePanel::left("left_panel") 38 | .resizable(true) 39 | .default_width(150.0) 40 | .width_range(80.0..=200.0) 41 | .show_inside(ui, |ui| { 42 | ui.vertical_centered(|ui| { 43 | ui.heading("Left Panel"); 44 | }); 45 | egui::ScrollArea::vertical().show(ui, |ui| { 46 | lorem_ipsum(ui); 47 | }); 48 | }); 49 | 50 | egui::SidePanel::right("right_panel") 51 | .resizable(true) 52 | .default_width(150.0) 53 | .width_range(80.0..=200.0) 54 | .show_inside(ui, |ui| { 55 | ui.vertical_centered(|ui| { 56 | ui.heading("Right Panel"); 57 | }); 58 | egui::ScrollArea::vertical().show(ui, |ui| { 59 | lorem_ipsum(ui); 60 | }); 61 | }); 62 | 63 | egui::TopBottomPanel::bottom("bottom_panel") 64 | .resizable(false) 65 | .min_height(0.0) 66 | .show_inside(ui, |ui| { 67 | ui.vertical_centered(|ui| { 68 | ui.heading("Bottom Panel"); 69 | }); 70 | }); 71 | 72 | egui::CentralPanel::default().show_inside(ui, |ui| { 73 | ui.vertical_centered(|ui| { 74 | ui.heading("Central Panel"); 75 | }); 76 | egui::ScrollArea::vertical().show(ui, |ui| { 77 | lorem_ipsum(ui); 78 | }); 79 | }); 80 | } 81 | } 82 | 83 | fn lorem_ipsum(ui: &mut egui::Ui) { 84 | ui.with_layout( 85 | egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true), 86 | |ui| { 87 | ui.label(egui::RichText::new(crate::LOREM_IPSUM_LONG).small().weak()); 88 | ui.add(egui::Separator::default().grow(8.0)); 89 | ui.label(egui::RichText::new(crate::LOREM_IPSUM_LONG).small().weak()); 90 | }, 91 | ); 92 | } 93 | -------------------------------------------------------------------------------- /crates/egui_demo_lib/src/easy_mark/mod.rs: -------------------------------------------------------------------------------- 1 | //! Experimental markup language 2 | 3 | mod easy_mark_editor; 4 | mod easy_mark_highlighter; 5 | pub mod easy_mark_parser; 6 | mod easy_mark_viewer; 7 | 8 | pub use easy_mark_editor::EasyMarkEditor; 9 | pub use easy_mark_highlighter::MemoizedEasymarkHighlighter; 10 | pub use easy_mark_parser as parser; 11 | pub use easy_mark_viewer::easy_mark; 12 | -------------------------------------------------------------------------------- /crates/egui_extras/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for egui_extras 2 | All notable changes to the `egui_extras` integration will be noted in this file. 3 | 4 | 5 | ## Unreleased 6 | 7 | 8 | ## 0.20.0 - 2022-12-08 9 | * Added `RetainedImage::from_svg_bytes_with_size` to be able to specify a size for SVGs to be rasterized at. 10 | * Lots of `Table` improvements ([#2369](https://github.com/emilk/egui/pull/2369)): 11 | * Double-click column separators to auto-size the column. 12 | * All `Table` now store state. You may see warnings about reused table ids. Use `ui.push_id` to fix this. 13 | * `TableBuilder::column` takes a `Column` instead of a `Size`. 14 | * `Column` controls default size, size range, resizing, and clipping of columns. 15 | * `Column::auto` will pick a size automatically 16 | * Added `Table::scroll_to_row`. 17 | * Added `Table::min_scrolled_height` and `Table::max_scroll_height`. 18 | * Added `TableBody::max_size`. 19 | * `Table::scroll` renamed to `Table::vscroll`. 20 | * `egui_extras::Strip` now has `clip: false` by default. 21 | * Fix bugs when putting `Table` inside of a horizontal `ScrollArea`. 22 | * Many other bug fixes. 23 | * Add `Table::auto_shrink` - set to `false` to expand table to fit its containing `Ui` ([#2371](https://github.com/emilk/egui/pull/2371)). 24 | * Added `TableBuilder::vertical_scroll_offset`: method to set vertical scroll offset position for a table ([#1946](https://github.com/emilk/egui/pull/1946)). 25 | 26 | 27 | ## 0.19.0 - 2022-08-20 28 | * MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)). 29 | * You can now specify a texture filter for `RetainedImage` ([#1636](https://github.com/emilk/egui/pull/1636)). 30 | * Fixed uneven `Table` striping ([#1680](https://github.com/emilk/egui/pull/1680)). 31 | 32 | 33 | ## 0.18.0 - 2022-04-30 34 | * Added `Strip`, `Table` and `DatePicker` ([#963](https://github.com/emilk/egui/pull/963)). 35 | * MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)). 36 | * Renamed feature "persistence" to "serde" ([#1540](https://github.com/emilk/egui/pull/1540)). 37 | 38 | 39 | ## 0.17.0 - 2022-02-22 40 | * `RetainedImage`: convenience for loading svg, png, jpeg etc and showing them in egui. 41 | -------------------------------------------------------------------------------- /crates/egui_extras/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_extras" 3 | version = "0.20.0" 4 | authors = [ 5 | "Dominik Rössler ", 6 | "Emil Ernerfeldt ", 7 | "René Rössler ", 8 | ] 9 | description = "Extra functionality and widgets for the egui GUI library" 10 | edition = "2021" 11 | rust-version = "1.65" 12 | homepage = "https://github.com/emilk/egui" 13 | license = "MIT OR Apache-2.0" 14 | readme = "README.md" 15 | repository = "https://github.com/emilk/egui" 16 | categories = ["gui", "game-development"] 17 | keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] 18 | include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] 19 | 20 | [package.metadata.docs.rs] 21 | all-features = true 22 | 23 | [lib] 24 | 25 | 26 | [features] 27 | default = [] 28 | 29 | ## Enable [`DatePickerButton`] widget. 30 | datepicker = ["chrono"] 31 | 32 | ## Support loading svg images. 33 | svg = ["resvg", "tiny-skia", "usvg"] 34 | 35 | ## Log warnings using `tracing` crate. 36 | tracing = ["dep:tracing", "egui/tracing"] 37 | 38 | 39 | [dependencies] 40 | egui = { version = "0.20.0", path = "../egui", default-features = false } 41 | 42 | serde = { version = "1", features = ["derive"] } 43 | 44 | #! ### Optional dependencies 45 | 46 | # Date operations needed for datepicker widget 47 | chrono = { version = "0.4", optional = true } 48 | 49 | ## Enable this when generating docs. 50 | document-features = { version = "0.2", optional = true } 51 | 52 | ## Add support for loading images with the [`image`](https://docs.rs/image) crate. 53 | ## 54 | ## You also need to ALSO opt-in to the image formats you want to support, like so: 55 | ## ```toml 56 | ## image = { version = "0.24", features = ["jpeg", "png"] } 57 | ## ``` 58 | image = { version = "0.24", optional = true, default-features = false } 59 | 60 | # svg feature 61 | resvg = { version = "0.23", optional = true } 62 | tiny-skia = { version = "0.6", optional = true } # must be updated in lock-step with resvg 63 | usvg = { version = "0.23", optional = true } 64 | 65 | # feature "tracing" 66 | tracing = { version = "0.1", optional = true, default-features = false, features = [ 67 | "std", 68 | ] } 69 | -------------------------------------------------------------------------------- /crates/egui_extras/README.md: -------------------------------------------------------------------------------- 1 | # egui_extras 2 | 3 | [![Latest version](https://img.shields.io/crates/v/egui_extras.svg)](https://crates.io/crates/egui_extras) 4 | [![Documentation](https://docs.rs/egui_extras/badge.svg)](https://docs.rs/egui_extras) 5 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 6 | ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) 7 | 8 | This is a crate that adds some features on top top of [`egui`](https://github.com/emilk/egui). This crate is for experimental features, and features that require big dependencies that do not belong in `egui`. 9 | -------------------------------------------------------------------------------- /crates/egui_extras/src/datepicker/mod.rs: -------------------------------------------------------------------------------- 1 | mod button; 2 | mod popup; 3 | 4 | pub use button::DatePickerButton; 5 | use chrono::{Datelike, Duration, NaiveDate, Weekday}; 6 | 7 | #[derive(Debug)] 8 | struct Week { 9 | number: u8, 10 | days: Vec, 11 | } 12 | 13 | fn month_data(year: i32, month: u32) -> Vec { 14 | let first = NaiveDate::from_ymd_opt(year, month, 1).expect("Could not create NaiveDate"); 15 | let mut start = first; 16 | while start.weekday() != Weekday::Mon { 17 | start = start.checked_sub_signed(Duration::days(1)).unwrap(); 18 | } 19 | let mut weeks = vec![]; 20 | let mut week = vec![]; 21 | while start < first || start.month() == first.month() || start.weekday() != Weekday::Mon { 22 | week.push(start); 23 | 24 | if start.weekday() == Weekday::Sun { 25 | weeks.push(Week { 26 | number: start.iso_week().week() as u8, 27 | days: week.drain(..).collect(), 28 | }); 29 | } 30 | start = start.checked_add_signed(Duration::days(1)).unwrap(); 31 | } 32 | 33 | weeks 34 | } 35 | -------------------------------------------------------------------------------- /crates/egui_extras/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a crate that adds some features on top top of [`egui`](https://github.com/emilk/egui). 2 | //! 3 | //! This crate are for experimental features, and features that require big dependencies that does not belong in `egui`. 4 | //! 5 | //! ## Feature flags 6 | #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] 7 | //! 8 | 9 | #![allow(clippy::float_cmp)] 10 | #![allow(clippy::manual_range_contains)] 11 | 12 | #[cfg(feature = "chrono")] 13 | mod datepicker; 14 | 15 | pub mod image; 16 | mod layout; 17 | mod sizing; 18 | mod strip; 19 | mod table; 20 | 21 | #[cfg(feature = "chrono")] 22 | pub use crate::datepicker::DatePickerButton; 23 | 24 | pub use crate::image::RetainedImage; 25 | pub(crate) use crate::layout::StripLayout; 26 | pub use crate::sizing::Size; 27 | pub use crate::strip::*; 28 | pub use crate::table::*; 29 | 30 | /// Log an error with either `tracing` or `eprintln` 31 | macro_rules! log_err { 32 | ($fmt: literal, $($arg: tt)*) => {{ 33 | #[cfg(feature = "tracing")] 34 | tracing::error!($fmt, $($arg)*); 35 | 36 | #[cfg(not(feature = "tracing"))] 37 | eprintln!( 38 | concat!("egui_extras: ", $fmt), $($arg)* 39 | ); 40 | }}; 41 | } 42 | pub(crate) use log_err; 43 | 44 | /// Panic in debug builds, log otherwise. 45 | macro_rules! log_or_panic { 46 | ($fmt: literal, $($arg: tt)*) => {{ 47 | if cfg!(debug_assertions) { 48 | panic!($fmt, $($arg)*); 49 | } else { 50 | $crate::log_err!($fmt, $($arg)*); 51 | } 52 | }}; 53 | } 54 | pub(crate) use log_or_panic; 55 | -------------------------------------------------------------------------------- /crates/egui_glium/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_glium" 3 | version = "0.20.1" 4 | authors = ["Emil Ernerfeldt "] 5 | description = "Bindings for using egui natively using the glium library" 6 | edition = "2021" 7 | rust-version = "1.65" 8 | homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glium" 9 | license = "MIT OR Apache-2.0" 10 | readme = "README.md" 11 | repository = "https://github.com/emilk/egui/tree/master/crates/egui_glium" 12 | categories = ["gui", "game-development"] 13 | keywords = ["glium", "egui", "gui", "gamedev"] 14 | include = [ 15 | "../LICENSE-APACHE", 16 | "../LICENSE-MIT", 17 | "**/*.rs", 18 | "Cargo.toml", 19 | "src/shader/*.glsl", 20 | ] 21 | 22 | [package.metadata.docs.rs] 23 | all-features = true 24 | 25 | 26 | [features] 27 | default = ["clipboard", "links"] 28 | 29 | ## Enable cut/copy/paste to OS clipboard. 30 | ## 31 | ## If disabled a clipboard will be simulated so you can still copy/paste within the egui app. 32 | clipboard = ["egui-winit/clipboard"] 33 | 34 | ## Enable opening links in a browser when an egui hyperlink is clicked. 35 | links = ["egui-winit/links"] 36 | 37 | 38 | [dependencies] 39 | egui = { version = "0.20.0", path = "../egui", default-features = false, features = [ 40 | "bytemuck", 41 | ] } 42 | egui-winit = { version = "0.20.0", path = "../egui-winit", default-features = false } 43 | 44 | ahash = { version = "0.8.1", default-features = false, features = [ 45 | "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead 46 | "std", 47 | ] } 48 | bytemuck = "1.7" 49 | glium = "0.32" 50 | 51 | #! ### Optional dependencies 52 | ## Enable this when generating docs. 53 | document-features = { version = "0.2", optional = true } 54 | 55 | 56 | [dev-dependencies] 57 | egui_demo_lib = { version = "0.20.0", path = "../egui_demo_lib", default-features = false } 58 | image = { version = "0.24", default-features = false, features = ["png"] } 59 | -------------------------------------------------------------------------------- /crates/egui_glium/README.md: -------------------------------------------------------------------------------- 1 | # egui_glium 2 | 3 | [![Latest version](https://img.shields.io/crates/v/egui_glium.svg)](https://crates.io/crates/egui_glium) 4 | [![Documentation](https://docs.rs/egui_glium/badge.svg)](https://docs.rs/egui_glium) 5 | [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) 6 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 7 | ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) 8 | 9 | This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [glium](https://crates.io/crates/glium) which allows you to write GUI code using egui and compile it and run it natively, cross platform. 10 | 11 | To use on Linux, first run: 12 | 13 | ``` 14 | sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev 15 | ``` 16 | 17 | This crate depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit). 18 | 19 | 20 | ## DEPRECATED - Looking for new maintainer 21 | This crate is no longer being updated. If you are interested in keeping `egui_glium` updated, then fork it to its own repository, make a PR to the egui repo removing it, and then I will give you access to it on crates.io so you can publish new `egui_glium` crates. 22 | -------------------------------------------------------------------------------- /crates/egui_glium/examples/pure_glium.rs: -------------------------------------------------------------------------------- 1 | //! Example how to use `egui_glium`. 2 | 3 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release 4 | 5 | use glium::glutin; 6 | 7 | fn main() { 8 | let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build(); 9 | let display = create_display(&event_loop); 10 | 11 | let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop); 12 | 13 | let mut color_test = egui_demo_lib::ColorTest::default(); 14 | 15 | event_loop.run(move |event, _, control_flow| { 16 | let mut redraw = || { 17 | let mut quit = false; 18 | 19 | let repaint_after = egui_glium.run(&display, |egui_ctx| { 20 | egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { 21 | ui.heading("Hello World!"); 22 | if ui.button("Quit").clicked() { 23 | quit = true; 24 | } 25 | }); 26 | 27 | egui::CentralPanel::default().show(egui_ctx, |ui| { 28 | egui::ScrollArea::vertical().show(ui, |ui| { 29 | color_test.ui(ui); 30 | }); 31 | }); 32 | }); 33 | 34 | *control_flow = if quit { 35 | glutin::event_loop::ControlFlow::Exit 36 | } else if repaint_after.is_zero() { 37 | display.gl_window().window().request_redraw(); 38 | glutin::event_loop::ControlFlow::Poll 39 | } else if let Some(repaint_after_instant) = 40 | std::time::Instant::now().checked_add(repaint_after) 41 | { 42 | glutin::event_loop::ControlFlow::WaitUntil(repaint_after_instant) 43 | } else { 44 | glutin::event_loop::ControlFlow::Wait 45 | }; 46 | 47 | { 48 | use glium::Surface as _; 49 | let mut target = display.draw(); 50 | 51 | let color = egui::Rgba::from_rgb(0.1, 0.3, 0.2); 52 | target.clear_color(color[0], color[1], color[2], color[3]); 53 | 54 | // draw things behind egui here 55 | 56 | egui_glium.paint(&display, &mut target); 57 | 58 | // draw things on top of egui here 59 | 60 | target.finish().unwrap(); 61 | } 62 | }; 63 | 64 | match event { 65 | // Platform-dependent event handlers to workaround a winit bug 66 | // See: https://github.com/rust-windowing/winit/issues/987 67 | // See: https://github.com/rust-windowing/winit/issues/1619 68 | glutin::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), 69 | glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), 70 | 71 | glutin::event::Event::WindowEvent { event, .. } => { 72 | use glutin::event::WindowEvent; 73 | if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) { 74 | *control_flow = glutin::event_loop::ControlFlow::Exit; 75 | } 76 | 77 | let event_response = egui_glium.on_event(&event); 78 | 79 | if event_response.repaint { 80 | display.gl_window().window().request_redraw(); 81 | } 82 | } 83 | glutin::event::Event::NewEvents(glutin::event::StartCause::ResumeTimeReached { 84 | .. 85 | }) => { 86 | display.gl_window().window().request_redraw(); 87 | } 88 | _ => (), 89 | } 90 | }); 91 | } 92 | 93 | fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { 94 | let window_builder = glutin::window::WindowBuilder::new() 95 | .with_resizable(true) 96 | .with_inner_size(glutin::dpi::LogicalSize { 97 | width: 800.0, 98 | height: 600.0, 99 | }) 100 | .with_title("egui_glium example"); 101 | 102 | let context_builder = glutin::ContextBuilder::new() 103 | .with_depth_buffer(0) 104 | .with_stencil_buffer(0) 105 | .with_vsync(true); 106 | 107 | glium::Display::new(window_builder, context_builder, event_loop).unwrap() 108 | } 109 | -------------------------------------------------------------------------------- /crates/egui_glium/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [`egui`] bindings for [`glium`](https://github.com/glium/glium). 2 | //! 3 | //! The main type you want to use is [`EguiGlium`]. 4 | //! 5 | //! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. 6 | //! 7 | //! ## Feature flags 8 | #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] 9 | //! 10 | 11 | #![allow(clippy::float_cmp)] 12 | #![allow(clippy::manual_range_contains)] 13 | 14 | mod painter; 15 | pub use painter::Painter; 16 | 17 | pub use egui_winit; 18 | 19 | use egui_winit::winit::event_loop::EventLoopWindowTarget; 20 | pub use egui_winit::EventResponse; 21 | 22 | // ---------------------------------------------------------------------------- 23 | 24 | /// Convenience wrapper for using [`egui`] from a [`glium`] app. 25 | pub struct EguiGlium { 26 | pub egui_ctx: egui::Context, 27 | pub egui_winit: egui_winit::State, 28 | pub painter: crate::Painter, 29 | 30 | shapes: Vec, 31 | textures_delta: egui::TexturesDelta, 32 | } 33 | 34 | impl EguiGlium { 35 | pub fn new(display: &glium::Display, event_loop: &EventLoopWindowTarget) -> Self { 36 | let painter = crate::Painter::new(display); 37 | 38 | let mut egui_winit = egui_winit::State::new(event_loop); 39 | egui_winit.set_max_texture_side(painter.max_texture_side()); 40 | let pixels_per_point = display.gl_window().window().scale_factor() as f32; 41 | egui_winit.set_pixels_per_point(pixels_per_point); 42 | 43 | Self { 44 | egui_ctx: Default::default(), 45 | egui_winit, 46 | painter, 47 | shapes: Default::default(), 48 | textures_delta: Default::default(), 49 | } 50 | } 51 | 52 | pub fn on_event(&mut self, event: &glium::glutin::event::WindowEvent<'_>) -> EventResponse { 53 | self.egui_winit.on_event(&self.egui_ctx, event) 54 | } 55 | 56 | /// Returns `true` if egui requests a repaint. 57 | /// 58 | /// Call [`Self::paint`] later to paint. 59 | pub fn run( 60 | &mut self, 61 | display: &glium::Display, 62 | run_ui: impl FnMut(&egui::Context), 63 | ) -> std::time::Duration { 64 | let raw_input = self 65 | .egui_winit 66 | .take_egui_input(display.gl_window().window()); 67 | let egui::FullOutput { 68 | platform_output, 69 | repaint_after, 70 | textures_delta, 71 | shapes, 72 | } = self.egui_ctx.run(raw_input, run_ui); 73 | 74 | self.egui_winit.handle_platform_output( 75 | display.gl_window().window(), 76 | &self.egui_ctx, 77 | platform_output, 78 | ); 79 | 80 | self.shapes = shapes; 81 | self.textures_delta.append(textures_delta); 82 | 83 | repaint_after 84 | } 85 | 86 | /// Paint the results of the last call to [`Self::run`]. 87 | pub fn paint(&mut self, display: &glium::Display, target: &mut T) { 88 | let shapes = std::mem::take(&mut self.shapes); 89 | let textures_delta = std::mem::take(&mut self.textures_delta); 90 | let clipped_primitives = self.egui_ctx.tessellate(shapes); 91 | self.painter.paint_and_update_textures( 92 | display, 93 | target, 94 | self.egui_ctx.pixels_per_point(), 95 | &clipped_primitives, 96 | &textures_delta, 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /crates/egui_glium/src/shader/fragment_100es.glsl: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | precision mediump float; 4 | uniform sampler2D u_sampler; 5 | varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA 6 | varying vec2 v_tc; 7 | 8 | // 0-255 sRGB from 0-1 linear 9 | vec3 srgb_from_linear(vec3 rgb) { 10 | bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); 11 | vec3 lower = rgb * vec3(3294.6); 12 | vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); 13 | return mix(higher, lower, vec3(cutoff)); 14 | } 15 | 16 | vec4 srgba_from_linear(vec4 rgba) { 17 | return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); 18 | } 19 | 20 | // 0-1 linear from 0-255 sRGB 21 | vec3 linear_from_srgb(vec3 srgb) { 22 | bvec3 cutoff = lessThan(srgb, vec3(10.31475)); 23 | vec3 lower = srgb / vec3(3294.6); 24 | vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); 25 | return mix(higher, lower, vec3(cutoff)); 26 | } 27 | 28 | vec4 linear_from_srgba(vec4 srgba) { 29 | return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); 30 | } 31 | 32 | void main() { 33 | // WebGL doesn't come with sRGBA textures: 34 | vec4 texture_in_gamma = texture2D(u_sampler, v_tc); 35 | 36 | // Multiply vertex color with texture color (in gamma space). 37 | gl_FragColor = v_rgba_gamma * texture_in_gamma; 38 | } 39 | -------------------------------------------------------------------------------- /crates/egui_glium/src/shader/fragment_120.glsl: -------------------------------------------------------------------------------- 1 | #version 120 2 | 3 | uniform sampler2D u_sampler; 4 | varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA 5 | varying vec2 v_tc; 6 | 7 | // 0-255 sRGB from 0-1 linear 8 | vec3 srgb_from_linear(vec3 rgb) { 9 | bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); 10 | vec3 lower = rgb * vec3(3294.6); 11 | vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); 12 | return mix(higher, lower, vec3(cutoff)); 13 | } 14 | 15 | // 0-255 sRGBA from 0-1 linear 16 | vec4 srgba_from_linear(vec4 rgba) { 17 | return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); 18 | } 19 | 20 | // 0-1 gamma from 0-1 linear 21 | vec4 gamma_from_linear_rgba(vec4 linear_rgba) { 22 | return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); 23 | } 24 | 25 | void main() { 26 | // The texture is set up with `SRGB8_ALPHA8` 27 | vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc)); 28 | 29 | // Multiply vertex color with texture color (in gamma space). 30 | gl_FragColor = v_rgba_gamma * texture_in_gamma; 31 | } 32 | -------------------------------------------------------------------------------- /crates/egui_glium/src/shader/fragment_140.glsl: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | uniform sampler2D u_sampler; 4 | in vec4 v_rgba_gamma; 5 | in vec2 v_tc; 6 | out vec4 f_color; 7 | 8 | // 0-255 sRGB from 0-1 linear 9 | vec3 srgb_from_linear(vec3 rgb) { 10 | bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); 11 | vec3 lower = rgb * vec3(3294.6); 12 | vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); 13 | return mix(higher, lower, vec3(cutoff)); 14 | } 15 | 16 | // 0-255 sRGBA from 0-1 linear 17 | vec4 srgba_from_linear(vec4 rgba) { 18 | return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); 19 | } 20 | 21 | // 0-1 gamma from 0-1 linear 22 | vec4 gamma_from_linear_rgba(vec4 linear_rgba) { 23 | return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); 24 | } 25 | 26 | void main() { 27 | // The texture is set up with `SRGB8_ALPHA8` 28 | vec4 texture_in_gamma = gamma_from_linear_rgba(texture(u_sampler, v_tc)); 29 | 30 | // Multiply vertex color with texture color (in gamma space). 31 | f_color = v_rgba_gamma * texture_in_gamma; 32 | } 33 | -------------------------------------------------------------------------------- /crates/egui_glium/src/shader/fragment_300es.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision mediump float; 4 | uniform sampler2D u_sampler; 5 | varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA 6 | varying vec2 v_tc; 7 | 8 | // 0-255 sRGB from 0-1 linear 9 | vec3 srgb_from_linear(vec3 rgb) { 10 | bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); 11 | vec3 lower = rgb * vec3(3294.6); 12 | vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); 13 | return mix(higher, lower, vec3(cutoff)); 14 | } 15 | 16 | // 0-255 sRGBA from 0-1 linear 17 | vec4 srgba_from_linear(vec4 rgba) { 18 | return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); 19 | } 20 | 21 | // 0-1 gamma from 0-1 linear 22 | vec4 gamma_from_linear_rgba(vec4 linear_rgba) { 23 | return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); 24 | } 25 | 26 | void main() { 27 | // The texture is set up with `SRGB8_ALPHA8` 28 | vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc)); 29 | 30 | // Multiply vertex color with texture color (in gamma space). 31 | gl_FragColor = v_rgba_gamma * texture_in_gamma; 32 | } 33 | -------------------------------------------------------------------------------- /crates/egui_glium/src/shader/vertex_100es.glsl: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | precision mediump float; 4 | uniform vec2 u_screen_size; 5 | attribute vec2 a_pos; 6 | attribute vec2 a_tc; 7 | attribute vec4 a_srgba; 8 | varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA 9 | varying vec2 v_tc; 10 | 11 | void main() { 12 | gl_Position = vec4( 13 | 2.0 * a_pos.x / u_screen_size.x - 1.0, 14 | 1.0 - 2.0 * a_pos.y / u_screen_size.y, 15 | 0.0, 16 | 1.0); 17 | v_rgba_gamma = a_srgba / 255.0; 18 | v_tc = a_tc; 19 | } 20 | -------------------------------------------------------------------------------- /crates/egui_glium/src/shader/vertex_120.glsl: -------------------------------------------------------------------------------- 1 | #version 120 2 | 3 | uniform vec2 u_screen_size; 4 | attribute vec2 a_pos; 5 | attribute vec4 a_srgba; // 0-255 sRGB 6 | attribute vec2 a_tc; 7 | varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA 8 | varying vec2 v_tc; 9 | 10 | void main() { 11 | gl_Position = vec4( 12 | 2.0 * a_pos.x / u_screen_size.x - 1.0, 13 | 1.0 - 2.0 * a_pos.y / u_screen_size.y, 14 | 0.0, 15 | 1.0); 16 | v_rgba_gamma = a_srgba / 255.0; 17 | v_tc = a_tc; 18 | } 19 | -------------------------------------------------------------------------------- /crates/egui_glium/src/shader/vertex_140.glsl: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | uniform vec2 u_screen_size; 4 | in vec2 a_pos; 5 | in vec4 a_srgba; // 0-255 sRGB 6 | in vec2 a_tc; 7 | out vec4 v_rgba_gamma; 8 | out vec2 v_tc; 9 | 10 | void main() { 11 | gl_Position = vec4( 12 | 2.0 * a_pos.x / u_screen_size.x - 1.0, 13 | 1.0 - 2.0 * a_pos.y / u_screen_size.y, 14 | 0.0, 15 | 1.0); 16 | v_rgba_gamma = a_srgba / 255.0; 17 | v_tc = a_tc; 18 | } 19 | -------------------------------------------------------------------------------- /crates/egui_glium/src/shader/vertex_300es.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision mediump float; 4 | uniform vec2 u_screen_size; 5 | attribute vec2 a_pos; 6 | attribute vec2 a_tc; 7 | attribute vec4 a_srgba; 8 | varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA 9 | varying vec2 v_tc; 10 | 11 | void main() { 12 | gl_Position = vec4( 13 | 2.0 * a_pos.x / u_screen_size.x - 1.0, 14 | 1.0 - 2.0 * a_pos.y / u_screen_size.y, 15 | 0.0, 16 | 1.0); 17 | v_rgba_gamma = a_srgba / 255.0; 18 | v_tc = a_tc; 19 | } 20 | -------------------------------------------------------------------------------- /crates/egui_glow/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for egui_glow 2 | All notable changes to the `egui_glow` integration will be noted in this file. 3 | 4 | 5 | ## Unreleased 6 | * Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)). 7 | 8 | 9 | ## 0.20.1 - 2022-12-11 10 | * Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)). 11 | 12 | 13 | ## 0.20.0 - 2022-12-08 14 | * Allow empty textures. 15 | * Added `shader_version` variable on `EguiGlow::new` for easier cross compilling on different OpenGL | ES targets ([#1993](https://github.com/emilk/egui/pull/1993)). 16 | 17 | 18 | ## 0.19.0 - 2022-08-20 19 | * MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)). 20 | * `EguiGlow::new` now takes an `EventLoopWindowTarget` instead of a `winit::Window` ([#1634](https://github.com/emilk/egui/pull/1634)). 21 | * Use `Arc` for `glow::Context` instead of `Rc` ([#1640](https://github.com/emilk/egui/pull/1640)). 22 | * Fixed `glClear` on WebGL1 ([#1658](https://github.com/emilk/egui/pull/1658)). 23 | * Add `Painter::intermediate_fbo` which tells callbacks where to render. This is only needed if the callbacks use their own FBO:s and need to know what to restore to. 24 | 25 | 26 | ## 0.18.1 - 2022-05-05 27 | * Remove calls to `gl.get_error` in release builds to speed up rendering ([#1583](https://github.com/emilk/egui/pull/1583)). 28 | 29 | 30 | ## 0.18.0 - 2022-04-30 31 | * Improved logging on rendering failures. 32 | * Added new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`. 33 | * Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)). 34 | * MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)). 35 | * `clipboard`, `links`, `winit` are now all opt-in features ([#1467](https://github.com/emilk/egui/pull/1467)). 36 | * Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)). 37 | * Removed the features `dark-light`, `default_fonts` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)). 38 | 39 | 40 | ## 0.17.0 - 2022-02-22 41 | * `EguiGlow::run` no longer returns the shapes to paint, but stores them internally until you call `EguiGlow::paint` ([#1110](https://github.com/emilk/egui/pull/1110)). 42 | * Added `set_texture_filter` method to `Painter` ([#1041](https://github.com/emilk/egui/pull/1041)). 43 | * Fixed failure to run in Chrome ([#1092](https://github.com/emilk/egui/pull/1092)). 44 | * `EguiGlow::new` and `EguiGlow::paint` now takes `&winit::Window` ([#1151](https://github.com/emilk/egui/pull/1151)). 45 | * Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)). 46 | 47 | 48 | ## 0.16.0 - 2021-12-29 49 | * Made winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)). 50 | * Simplified `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)). 51 | * Removed `EguiGlow::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)). 52 | * Updated `glutin` to 0.28 ([#930](https://github.com/emilk/egui/pull/930)). 53 | * Changed the `Painter` interface slightly ([#999](https://github.com/emilk/egui/pull/999)). 54 | 55 | 56 | ## 0.15.0 - 2021-10-24 57 | `egui_glow` has been newly created, with feature parity to `egui_glium`. 58 | 59 | As `glow` is a set of lower-level bindings to OpenGL, this crate is potentially less stable than `egui_glium`, 60 | but hopefully this will one day replace `egui_glium` as the default backend for `eframe`. 61 | -------------------------------------------------------------------------------- /crates/egui_glow/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_glow" 3 | version = "0.20.1" 4 | authors = ["Emil Ernerfeldt "] 5 | description = "Bindings for using egui natively using the glow library" 6 | edition = "2021" 7 | rust-version = "1.65" 8 | homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glow" 9 | license = "MIT OR Apache-2.0" 10 | readme = "README.md" 11 | repository = "https://github.com/emilk/egui/tree/master/crates/egui_glow" 12 | categories = ["gui", "game-development"] 13 | keywords = ["glow", "egui", "gui", "gamedev"] 14 | include = [ 15 | "../LICENSE-APACHE", 16 | "../LICENSE-MIT", 17 | "**/*.rs", 18 | "Cargo.toml", 19 | "src/shader/*.glsl", 20 | ] 21 | 22 | [package.metadata.docs.rs] 23 | all-features = true 24 | 25 | 26 | [features] 27 | default = [] 28 | 29 | ## For the `winit` integration: 30 | ## enable cut/copy/paste to os clipboard. 31 | ## 32 | ## if disabled a clipboard will be simulated so you can still copy/paste within the egui app. 33 | clipboard = ["egui-winit?/clipboard"] 34 | 35 | ## For the `winit` integration: 36 | ## enable opening links in a browser when an egui hyperlink is clicked. 37 | links = ["egui-winit?/links"] 38 | 39 | ## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate. 40 | puffin = ["dep:puffin", "egui-winit?/puffin"] 41 | 42 | ## Enable [`winit`](https://docs.rs/winit) integration. 43 | winit = ["egui-winit"] 44 | 45 | 46 | [dependencies] 47 | egui = { version = "0.20.0", path = "../egui", default-features = false, features = [ 48 | "bytemuck", 49 | ] } 50 | 51 | bytemuck = "1.7" 52 | glow = "0.11" 53 | memoffset = "0.6" 54 | tracing = { version = "0.1", default-features = false, features = ["std"] } 55 | 56 | #! ### Optional dependencies 57 | ## Enable this when generating docs. 58 | document-features = { version = "0.2", optional = true } 59 | 60 | # Native: 61 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 62 | egui-winit = { version = "0.20.0", path = "../egui-winit", optional = true, default-features = false } 63 | puffin = { version = "0.14", optional = true } 64 | 65 | # Web: 66 | [target.'cfg(target_arch = "wasm32")'.dependencies] 67 | web-sys = { version = "0.3", features = ["console"] } 68 | wasm-bindgen = { version = "0.2" } 69 | 70 | 71 | [dev-dependencies] 72 | glutin = "0.30.2" # examples/pure_glow 73 | raw-window-handle = "0.5.0" 74 | 75 | 76 | [[example]] 77 | name = "pure_glow" 78 | required-features = ["winit", "egui/default_fonts"] 79 | -------------------------------------------------------------------------------- /crates/egui_glow/README.md: -------------------------------------------------------------------------------- 1 | # egui_glow 2 | 3 | [![Latest version](https://img.shields.io/crates/v/egui_glow.svg)](https://crates.io/crates/egui_glow) 4 | [![Documentation](https://docs.rs/egui_glow/badge.svg)](https://docs.rs/egui_glow) 5 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 6 | ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) 7 | 8 | This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [glow](https://crates.io/crates/glow) which allows you to: 9 | * Render egui using glow on both native and web. 10 | * Write cross platform native egui apps (with the `winit` feature). 11 | 12 | To write web apps using `glow` you can use [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) (which uses `egui_glow` for rendering). 13 | 14 | To use on Linux, first run: 15 | 16 | ``` 17 | sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev 18 | ``` 19 | 20 | This crate optionally depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit). 21 | -------------------------------------------------------------------------------- /crates/egui_glow/src/misc_util.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_code)] 2 | 3 | use glow::HasContext as _; 4 | 5 | pub(crate) unsafe fn compile_shader( 6 | gl: &glow::Context, 7 | shader_type: u32, 8 | source: &str, 9 | ) -> Result { 10 | let shader = gl.create_shader(shader_type)?; 11 | 12 | gl.shader_source(shader, source); 13 | 14 | gl.compile_shader(shader); 15 | 16 | if gl.get_shader_compile_status(shader) { 17 | Ok(shader) 18 | } else { 19 | Err(gl.get_shader_info_log(shader)) 20 | } 21 | } 22 | 23 | pub(crate) unsafe fn link_program<'a, T: IntoIterator>( 24 | gl: &glow::Context, 25 | shaders: T, 26 | ) -> Result { 27 | let program = gl.create_program()?; 28 | 29 | for shader in shaders { 30 | gl.attach_shader(program, *shader); 31 | } 32 | 33 | gl.link_program(program); 34 | 35 | if gl.get_program_link_status(program) { 36 | Ok(program) 37 | } else { 38 | Err(gl.get_program_info_log(program)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /crates/egui_glow/src/shader/fragment.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | 5 | uniform sampler2D u_sampler; 6 | 7 | #if NEW_SHADER_INTERFACE 8 | in vec4 v_rgba_in_gamma; 9 | in vec2 v_tc; 10 | out vec4 f_color; 11 | // a dirty hack applied to support webGL2 12 | #define gl_FragColor f_color 13 | #define texture2D texture 14 | #else 15 | varying vec4 v_rgba_in_gamma; 16 | varying vec2 v_tc; 17 | #endif 18 | 19 | // 0-1 sRGB gamma from 0-1 linear 20 | vec3 srgb_gamma_from_linear(vec3 rgb) { 21 | bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); 22 | vec3 lower = rgb * vec3(12.92); 23 | vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055); 24 | return mix(higher, lower, vec3(cutoff)); 25 | } 26 | 27 | // 0-1 sRGBA gamma from 0-1 linear 28 | vec4 srgba_gamma_from_linear(vec4 rgba) { 29 | return vec4(srgb_gamma_from_linear(rgba.rgb), rgba.a); 30 | } 31 | 32 | void main() { 33 | #if SRGB_TEXTURES 34 | vec4 texture_in_gamma = srgba_gamma_from_linear(texture2D(u_sampler, v_tc)); 35 | #else 36 | vec4 texture_in_gamma = texture2D(u_sampler, v_tc); 37 | #endif 38 | 39 | // We multiply the colors in gamma space, because that's the only way to get text to look right. 40 | gl_FragColor = v_rgba_in_gamma * texture_in_gamma; 41 | } 42 | -------------------------------------------------------------------------------- /crates/egui_glow/src/shader/vertex.glsl: -------------------------------------------------------------------------------- 1 | #if NEW_SHADER_INTERFACE 2 | #define I in 3 | #define O out 4 | #define V(x) x 5 | #else 6 | #define I attribute 7 | #define O varying 8 | #define V(x) vec3(x) 9 | #endif 10 | 11 | #ifdef GL_ES 12 | precision mediump float; 13 | #endif 14 | 15 | uniform vec2 u_screen_size; 16 | I vec2 a_pos; 17 | I vec4 a_srgba; // 0-255 sRGB 18 | I vec2 a_tc; 19 | O vec4 v_rgba_in_gamma; 20 | O vec2 v_tc; 21 | 22 | void main() { 23 | gl_Position = vec4( 24 | 2.0 * a_pos.x / u_screen_size.x - 1.0, 25 | 1.0 - 2.0 * a_pos.y / u_screen_size.y, 26 | 0.0, 27 | 1.0); 28 | v_rgba_in_gamma = a_srgba / 255.0; 29 | v_tc = a_tc; 30 | } 31 | -------------------------------------------------------------------------------- /crates/egui_glow/src/shader_version.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_code)] 2 | 3 | use std::convert::TryInto; 4 | 5 | /// Helper for parsing and interpreting the OpenGL shader version. 6 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 7 | #[allow(dead_code)] 8 | pub enum ShaderVersion { 9 | Gl120, 10 | 11 | /// OpenGL 1.4 or later 12 | Gl140, 13 | 14 | /// e.g. WebGL1 15 | Es100, 16 | 17 | /// e.g. WebGL2 18 | Es300, 19 | } 20 | 21 | impl ShaderVersion { 22 | pub fn get(gl: &glow::Context) -> Self { 23 | use glow::HasContext as _; 24 | let shading_lang_string = 25 | unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) }; 26 | let shader_version = Self::parse(&shading_lang_string); 27 | tracing::debug!( 28 | "Shader version: {:?} ({:?}).", 29 | shader_version, 30 | shading_lang_string 31 | ); 32 | shader_version 33 | } 34 | 35 | #[inline] 36 | pub(crate) fn parse(glsl_ver: &str) -> Self { 37 | let start = glsl_ver.find(|c| char::is_ascii_digit(&c)).unwrap(); 38 | let es = glsl_ver[..start].contains(" ES "); 39 | let ver = glsl_ver[start..] 40 | .split_once(' ') 41 | .map_or(&glsl_ver[start..], |x| x.0); 42 | let [maj, min]: [u8; 2] = ver 43 | .splitn(3, '.') 44 | .take(2) 45 | .map(|x| x.parse().unwrap_or_default()) 46 | .collect::>() 47 | .try_into() 48 | .unwrap(); 49 | if es { 50 | if maj >= 3 { 51 | Self::Es300 52 | } else { 53 | Self::Es100 54 | } 55 | } else if maj > 1 || (maj == 1 && min >= 40) { 56 | Self::Gl140 57 | } else { 58 | Self::Gl120 59 | } 60 | } 61 | 62 | /// Goes on top of the shader. 63 | pub fn version_declaration(&self) -> &'static str { 64 | match self { 65 | Self::Gl120 => "#version 120\n", 66 | Self::Gl140 => "#version 140\n", 67 | Self::Es100 => "#version 100\n", 68 | Self::Es300 => "#version 300 es\n", 69 | } 70 | } 71 | 72 | /// If true, use `in/out`. If `false`, use `varying` and `gl_FragColor`. 73 | pub fn is_new_shader_interface(&self) -> bool { 74 | match self { 75 | Self::Gl120 | Self::Es100 => false, 76 | Self::Es300 | Self::Gl140 => true, 77 | } 78 | } 79 | 80 | pub fn is_embedded(&self) -> bool { 81 | match self { 82 | Self::Gl120 | Self::Gl140 => false, 83 | Self::Es100 | Self::Es300 => true, 84 | } 85 | } 86 | } 87 | 88 | #[test] 89 | fn test_shader_version() { 90 | use ShaderVersion::{Es100, Es300, Gl120, Gl140}; 91 | for (s, v) in [ 92 | ("1.2 OpenGL foo bar", Gl120), 93 | ("3.0", Gl140), 94 | ("0.0", Gl120), 95 | ("OpenGL ES GLSL 3.00 (WebGL2)", Es300), 96 | ("OpenGL ES GLSL 1.00 (WebGL)", Es100), 97 | ("OpenGL ES GLSL ES 1.00 foo bar", Es100), 98 | ("WebGL GLSL ES 3.00 foo bar", Es300), 99 | ("WebGL GLSL ES 3.00", Es300), 100 | ("WebGL GLSL ES 1.0 foo bar", Es100), 101 | ] { 102 | assert_eq!(ShaderVersion::parse(s), v); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /crates/egui_glow/src/winit.rs: -------------------------------------------------------------------------------- 1 | use crate::shader_version::ShaderVersion; 2 | pub use egui_winit; 3 | use egui_winit::winit; 4 | pub use egui_winit::EventResponse; 5 | 6 | /// Use [`egui`] from a [`glow`] app based on [`winit`]. 7 | pub struct EguiGlow { 8 | pub egui_ctx: egui::Context, 9 | pub egui_winit: egui_winit::State, 10 | pub painter: crate::Painter, 11 | 12 | shapes: Vec, 13 | textures_delta: egui::TexturesDelta, 14 | } 15 | 16 | impl EguiGlow { 17 | /// For automatic shader version detection set `shader_version` to `None`. 18 | pub fn new( 19 | event_loop: &winit::event_loop::EventLoopWindowTarget, 20 | gl: std::sync::Arc, 21 | shader_version: Option, 22 | ) -> Self { 23 | let painter = crate::Painter::new(gl, "", shader_version) 24 | .map_err(|error| { 25 | tracing::error!("error occurred in initializing painter:\n{}", error); 26 | }) 27 | .unwrap(); 28 | 29 | Self { 30 | egui_ctx: Default::default(), 31 | egui_winit: egui_winit::State::new(event_loop), 32 | painter, 33 | shapes: Default::default(), 34 | textures_delta: Default::default(), 35 | } 36 | } 37 | 38 | pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) -> EventResponse { 39 | self.egui_winit.on_event(&self.egui_ctx, event) 40 | } 41 | 42 | /// Returns the `Duration` of the timeout after which egui should be repainted even if there's no new events. 43 | /// 44 | /// Call [`Self::paint`] later to paint. 45 | pub fn run( 46 | &mut self, 47 | window: &winit::window::Window, 48 | run_ui: impl FnMut(&egui::Context), 49 | ) -> std::time::Duration { 50 | let raw_input = self.egui_winit.take_egui_input(window); 51 | let egui::FullOutput { 52 | platform_output, 53 | repaint_after, 54 | textures_delta, 55 | shapes, 56 | } = self.egui_ctx.run(raw_input, run_ui); 57 | 58 | self.egui_winit 59 | .handle_platform_output(window, &self.egui_ctx, platform_output); 60 | 61 | self.shapes = shapes; 62 | self.textures_delta.append(textures_delta); 63 | repaint_after 64 | } 65 | 66 | /// Paint the results of the last call to [`Self::run`]. 67 | pub fn paint(&mut self, window: &winit::window::Window) { 68 | let shapes = std::mem::take(&mut self.shapes); 69 | let mut textures_delta = std::mem::take(&mut self.textures_delta); 70 | 71 | for (id, image_delta) in textures_delta.set { 72 | self.painter.set_texture(id, &image_delta); 73 | } 74 | 75 | let clipped_primitives = self.egui_ctx.tessellate(shapes); 76 | let dimensions: [u32; 2] = window.inner_size().into(); 77 | self.painter.paint_primitives( 78 | dimensions, 79 | self.egui_ctx.pixels_per_point(), 80 | &clipped_primitives, 81 | ); 82 | 83 | for id in textures_delta.free.drain(..) { 84 | self.painter.free_texture(id); 85 | } 86 | } 87 | 88 | /// Call to release the allocated graphics resources. 89 | pub fn destroy(&mut self) { 90 | self.painter.destroy(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/egui_web/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Legacy changelog for egui_web 2 | Between versions 0.17 and 0.18, `egui_web` was absorbed into `eframe`. Most of this changelog was then merged into [the `eframe` changelog](../eframe/CHANGELOG.md). This changelog is now only kept for historical reasons. 3 | 4 | 5 | 6 | ## 0.17.0 - 2022-02-22 7 | * The default painter is now glow instead of WebGL ([#1020](https://github.com/emilk/egui/pull/1020)). 8 | * Made the WebGL painter opt-in ([#1020](https://github.com/emilk/egui/pull/1020)). 9 | * Fixed glow failure on Chromium ([#1092](https://github.com/emilk/egui/pull/1092)). 10 | * Shift-scroll will now result in horizontal scrolling ([#1136](https://github.com/emilk/egui/pull/1136)). 11 | * Updated `epi::IntegrationInfo::web_location_hash` on `hashchange` event ([#1140](https://github.com/emilk/egui/pull/1140)). 12 | * Parse and percent-decode the web location query string ([#1258](https://github.com/emilk/egui/pull/1258)). 13 | 14 | 15 | ## 0.16.0 - 2021-12-29 16 | * Fixed [dark rendering in WebKitGTK](https://github.com/emilk/egui/issues/794) ([#888](https://github.com/emilk/egui/pull/888/)). 17 | * Added feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)). 18 | 19 | 20 | ## 0.15.0 - 2021-10-24 21 | ### Added 22 | * Remove "http" feature (use https://github.com/emilk/ehttp instead!). 23 | * Implement `epi::NativeTexture` trait for the WebGL painter. 24 | * Deprecate `Painter::register_webgl_texture. 25 | 26 | ### Fixed 🐛 27 | * Fixed multiline paste. 28 | * Fixed painting with non-opaque backgrounds. 29 | * Improve text input on mobile and for IME. 30 | 31 | 32 | ## 0.14.1 - 2021-08-28 33 | ### Fixed 🐛 34 | * Fixed alpha blending for WebGL2 and WebGL1 with sRGB support backends, now having identical results as egui_glium. 35 | * Fixed use of egui on devices with both touch and mouse. 36 | 37 | 38 | ## 0.14.0 - 2021-08-24 39 | ### Added ⭐ 40 | * Added support for dragging and dropping files into the browser window. 41 | 42 | ### Fixed 🐛 43 | * Made text thicker and less pixelated. 44 | 45 | 46 | ## 0.13.0 - 2021-06-24 47 | ### Changed 🔧 48 | * Default to light visuals unless the system reports a preference for dark mode. 49 | 50 | ### Fixed 🐛 51 | * Improve alpha blending, making fonts look much better (especially in light mode) 52 | * Fixed double-paste bug 53 | 54 | 55 | ## 0.12.0 - 2021-05-10 56 | ### Fixed 🐛 57 | * Scroll faster when scrolling with mouse wheel. 58 | 59 | 60 | ## 0.11.0 - 2021-04-05 61 | ### Added ⭐ 62 | * [Fix mobile and IME text input](https://github.com/emilk/egui/pull/253) 63 | * Hold down a modifier key when clicking a link to open it in a new tab. 64 | 65 | Contributors: [n2](https://github.com/n2) 66 | 67 | 68 | ## 0.10.0 - 2021-02-28 69 | ### Added ⭐ 70 | * You can control the maximum egui canvas size with `App::max_size_points`. 71 | 72 | 73 | ## 0.9.0 - 2021-02-07 74 | ### Added ⭐ 75 | * Right-clicks will no longer open browser context menu. 76 | 77 | ### Fixed 🐛 78 | * Fixed a bug where one couldn't select items in a combo box on a touch screen. 79 | 80 | 81 | ## 0.8.0 - 2021-01-17 82 | ### Added ⭐ 83 | * WebGL2 is now supported, with improved texture sampler. WebGL1 will be used as a fallback. 84 | 85 | ### Changed 🔧 86 | * Slightly improved alpha-blending (work-around for non-existing linear-space blending). 87 | 88 | ### Fixed 🐛 89 | * Call prevent_default for arrow keys when entering text 90 | 91 | 92 | ## 0.7.0 - 2021-01-04 93 | ### Changed 🔧 94 | * `http` and `persistence` are now optional (and opt-in) features. 95 | 96 | ### Fixed 🐛 97 | * egui_web now compiled without `RUSTFLAGS=--cfg=web_sys_unstable_apis`, but copy/paste won't work. 98 | 99 | 100 | ## 0.6.0 - 2020-12-26 101 | ### Added ⭐ 102 | * Auto-save of app state to local storage 103 | 104 | ### Changed 🔧 105 | * Set a maximum canvas size to alleviate performance issues on some machines 106 | * Simplify `egui_web::start` arguments 107 | 108 | 109 | ## 0.4.0 - 2020-11-28 110 | ### Added ⭐ 111 | * A simple HTTP fetch API (wraps `web_sys`). 112 | * Added ability to request a repaint 113 | * Copy/cut/paste suppoert 114 | 115 | ### Changed 🔧 116 | * Automatic repaint every second 117 | 118 | ### Fixed 🐛 119 | * Web browser zooming should now work as expected 120 | * A bunch of bug fixes related to keyboard events 121 | -------------------------------------------------------------------------------- /crates/egui_web/README.md: -------------------------------------------------------------------------------- 1 | `egui_web` used to be a standalone crate, but has now been moved into [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe). 2 | -------------------------------------------------------------------------------- /crates/emath/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "emath" 3 | version = "0.20.0" 4 | authors = ["Emil Ernerfeldt "] 5 | description = "Minimal 2D math library for GUI work" 6 | edition = "2021" 7 | rust-version = "1.65" 8 | homepage = "https://github.com/emilk/egui/tree/master/crates/emath" 9 | license = "MIT OR Apache-2.0" 10 | readme = "README.md" 11 | repository = "https://github.com/emilk/egui/tree/master/crates/emath" 12 | categories = ["mathematics", "gui"] 13 | keywords = ["math", "gui"] 14 | include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] 15 | 16 | [package.metadata.docs.rs] 17 | all-features = true 18 | 19 | [lib] 20 | 21 | 22 | [features] 23 | default = [] 24 | 25 | ## Enable additional checks if debug assertions are enabled (debug builds). 26 | extra_debug_asserts = [] 27 | ## Always enable additional checks. 28 | extra_asserts = [] 29 | 30 | 31 | [dependencies] 32 | #! ### Optional dependencies 33 | 34 | ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `emath` types to `&[u8]`. 35 | bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } 36 | 37 | ## Enable this when generating docs. 38 | document-features = { version = "0.2", optional = true } 39 | 40 | ## [`mint`](https://docs.rs/mint) enables interopability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra). 41 | mint = { version = "0.5.6", optional = true } 42 | 43 | ## Allow serialization using [`serde`](https://docs.rs/serde). 44 | serde = { version = "1", optional = true, features = ["derive"] } 45 | -------------------------------------------------------------------------------- /crates/emath/README.md: -------------------------------------------------------------------------------- 1 | # emath - egui math library 2 | 3 | A bare-bones 2D math library with types and functions useful for GUI building. 4 | 5 | Made for [`egui`](https://github.com/emilk/egui/). 6 | -------------------------------------------------------------------------------- /crates/emath/src/numeric.rs: -------------------------------------------------------------------------------- 1 | /// Implemented for all builtin numeric types 2 | pub trait Numeric: Clone + Copy + PartialEq + PartialOrd + 'static { 3 | /// Is this an integer type? 4 | const INTEGRAL: bool; 5 | 6 | /// Smallest finite value 7 | const MIN: Self; 8 | 9 | /// Largest finite value 10 | const MAX: Self; 11 | 12 | fn to_f64(self) -> f64; 13 | 14 | fn from_f64(num: f64) -> Self; 15 | } 16 | 17 | macro_rules! impl_numeric_float { 18 | ($t: ident) => { 19 | impl Numeric for $t { 20 | const INTEGRAL: bool = false; 21 | const MIN: Self = std::$t::MIN; 22 | const MAX: Self = std::$t::MAX; 23 | 24 | #[inline(always)] 25 | fn to_f64(self) -> f64 { 26 | #[allow(trivial_numeric_casts)] 27 | { 28 | self as f64 29 | } 30 | } 31 | 32 | #[inline(always)] 33 | fn from_f64(num: f64) -> Self { 34 | #[allow(trivial_numeric_casts)] 35 | { 36 | num as Self 37 | } 38 | } 39 | } 40 | }; 41 | } 42 | 43 | macro_rules! impl_numeric_integer { 44 | ($t: ident) => { 45 | impl Numeric for $t { 46 | const INTEGRAL: bool = true; 47 | const MIN: Self = std::$t::MIN; 48 | const MAX: Self = std::$t::MAX; 49 | 50 | #[inline(always)] 51 | fn to_f64(self) -> f64 { 52 | self as f64 53 | } 54 | 55 | #[inline(always)] 56 | fn from_f64(num: f64) -> Self { 57 | num as Self 58 | } 59 | } 60 | }; 61 | } 62 | 63 | impl_numeric_float!(f32); 64 | impl_numeric_float!(f64); 65 | impl_numeric_integer!(i8); 66 | impl_numeric_integer!(u8); 67 | impl_numeric_integer!(i16); 68 | impl_numeric_integer!(u16); 69 | impl_numeric_integer!(i32); 70 | impl_numeric_integer!(u32); 71 | impl_numeric_integer!(i64); 72 | impl_numeric_integer!(u64); 73 | impl_numeric_integer!(isize); 74 | impl_numeric_integer!(usize); 75 | -------------------------------------------------------------------------------- /crates/emath/src/rect_transform.rs: -------------------------------------------------------------------------------- 1 | use crate::{pos2, remap, remap_clamp, Pos2, Rect, Vec2}; 2 | 3 | /// Linearly transforms positions from one [`Rect`] to another. 4 | /// 5 | /// [`RectTransform`] stores the rectangles, and therefore supports clamping and culling. 6 | #[repr(C)] 7 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 8 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 9 | #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] 10 | pub struct RectTransform { 11 | from: Rect, 12 | to: Rect, 13 | } 14 | 15 | impl RectTransform { 16 | pub fn identity(from_and_to: Rect) -> Self { 17 | Self::from_to(from_and_to, from_and_to) 18 | } 19 | 20 | pub fn from_to(from: Rect, to: Rect) -> Self { 21 | Self { from, to } 22 | } 23 | 24 | pub fn from(&self) -> &Rect { 25 | &self.from 26 | } 27 | 28 | pub fn to(&self) -> &Rect { 29 | &self.to 30 | } 31 | 32 | /// The scale factors. 33 | pub fn scale(&self) -> Vec2 { 34 | self.to.size() / self.from.size() 35 | } 36 | 37 | pub fn inverse(&self) -> RectTransform { 38 | Self::from_to(self.to, self.from) 39 | } 40 | 41 | /// Transforms the given coordinate in the `from` space to the `to` space. 42 | pub fn transform_pos(&self, pos: Pos2) -> Pos2 { 43 | pos2( 44 | remap(pos.x, self.from.x_range(), self.to.x_range()), 45 | remap(pos.y, self.from.y_range(), self.to.y_range()), 46 | ) 47 | } 48 | 49 | /// Transforms the given rectangle in the `in`-space to a rectangle in the `out`-space. 50 | pub fn transform_rect(&self, rect: Rect) -> Rect { 51 | Rect { 52 | min: self.transform_pos(rect.min), 53 | max: self.transform_pos(rect.max), 54 | } 55 | } 56 | 57 | /// Transforms the given coordinate in the `from` space to the `to` space, 58 | /// clamping if necessary. 59 | pub fn transform_pos_clamped(&self, pos: Pos2) -> Pos2 { 60 | pos2( 61 | remap_clamp(pos.x, self.from.x_range(), self.to.x_range()), 62 | remap_clamp(pos.y, self.from.y_range(), self.to.y_range()), 63 | ) 64 | } 65 | } 66 | 67 | /// Transforms the position. 68 | impl std::ops::Mul for RectTransform { 69 | type Output = Pos2; 70 | 71 | fn mul(self, pos: Pos2) -> Pos2 { 72 | self.transform_pos(pos) 73 | } 74 | } 75 | 76 | /// Transforms the position. 77 | impl std::ops::Mul for &RectTransform { 78 | type Output = Pos2; 79 | 80 | fn mul(self, pos: Pos2) -> Pos2 { 81 | self.transform_pos(pos) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/epaint/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "epaint" 3 | version = "0.20.0" 4 | authors = ["Emil Ernerfeldt "] 5 | description = "Minimal 2D graphics library for GUI work" 6 | edition = "2021" 7 | rust-version = "1.65" 8 | homepage = "https://github.com/emilk/egui/tree/master/crates/epaint" 9 | license = "(MIT OR Apache-2.0) AND OFL-1.1 AND LicenseRef-UFL-1.0" # OFL and UFL used by default_fonts. See https://github.com/emilk/egui/issues/2321 10 | readme = "README.md" 11 | repository = "https://github.com/emilk/egui/tree/master/crates/epaint" 12 | categories = ["graphics", "gui"] 13 | keywords = ["graphics", "gui", "egui"] 14 | include = [ 15 | "../LICENSE-APACHE", 16 | "../LICENSE-MIT", 17 | "**/*.rs", 18 | "Cargo.toml", 19 | "fonts/*.ttf", 20 | "fonts/*.txt", 21 | "fonts/OFL.txt", 22 | "fonts/UFL.txt", 23 | ] 24 | 25 | [package.metadata.docs.rs] 26 | all-features = true 27 | 28 | [lib] 29 | 30 | 31 | [features] 32 | default = ["default_fonts"] 33 | 34 | ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`Vertex`] to `&[u8]`. 35 | bytemuck = ["dep:bytemuck", "emath/bytemuck", "ecolor/bytemuck"] 36 | 37 | ## [`cint`](https://docs.rs/cint) enables interopability with other color libraries. 38 | cint = ["ecolor/cint"] 39 | 40 | ## Enable the [`hex_color`] macro. 41 | color-hex = ["ecolor/color-hex"] 42 | 43 | ## This will automatically detect deadlocks due to double-locking on the same thread. 44 | ## If your app freezes, you may want to enable this! 45 | ## Only affects [`mutex::RwLock`] (which epaint and egui uses a lot). 46 | deadlock_detection = ["dep:backtrace"] 47 | 48 | ## If set, epaint will use `include_bytes!` to bundle some fonts. 49 | ## If you plan on specifying your own fonts you may disable this feature. 50 | default_fonts = [] 51 | 52 | ## Enable additional checks if debug assertions are enabled (debug builds). 53 | extra_debug_asserts = [ 54 | "emath/extra_debug_asserts", 55 | "ecolor/extra_debug_asserts", 56 | ] 57 | ## Always enable additional checks. 58 | extra_asserts = ["emath/extra_asserts", "ecolor/extra_asserts"] 59 | 60 | ## [`mint`](https://docs.rs/mint) enables interopability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra). 61 | mint = ["emath/mint"] 62 | 63 | ## Allow serialization using [`serde`](https://docs.rs/serde). 64 | serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde"] 65 | 66 | [dependencies] 67 | emath = { version = "0.20.0", path = "../emath" } 68 | ecolor = { version = "0.20.0", path = "../ecolor" } 69 | 70 | ab_glyph = "0.2.11" 71 | ahash = { version = "0.8.1", default-features = false, features = [ 72 | "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead 73 | "std", 74 | ] } 75 | nohash-hasher = "0.2" 76 | 77 | #! ### Optional dependencies 78 | bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } 79 | 80 | ## Enable this when generating docs. 81 | document-features = { version = "0.2", optional = true } 82 | 83 | ## Allow serialization using [`serde`](https://docs.rs/serde) . 84 | serde = { version = "1", optional = true, features = ["derive", "rc"] } 85 | 86 | # native: 87 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 88 | backtrace = { version = "0.3", optional = true } 89 | parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. 90 | 91 | # web: 92 | [target.'cfg(target_arch = "wasm32")'.dependencies] 93 | atomic_refcell = "0.1" # Used instead of parking_lot on on wasm. See https://github.com/emilk/egui/issues/1401 94 | 95 | 96 | [dev-dependencies] 97 | criterion = { version = "0.4", default-features = false } 98 | 99 | 100 | [[bench]] 101 | name = "benchmark" 102 | harness = false 103 | -------------------------------------------------------------------------------- /crates/epaint/README.md: -------------------------------------------------------------------------------- 1 | # epaint - egui paint library 2 | 3 | A bare-bones 2D graphics library for turning simple 2D shapes and text into textured triangles. 4 | 5 | Made for [`egui`](https://github.com/emilk/egui/). 6 | -------------------------------------------------------------------------------- /crates/epaint/benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use epaint::*; 4 | 5 | fn single_dashed_lines(c: &mut Criterion) { 6 | c.bench_function("single_dashed_lines", move |b| { 7 | b.iter(|| { 8 | let mut v = Vec::new(); 9 | 10 | let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; 11 | 12 | for _ in 0..100 { 13 | v.extend(Shape::dashed_line( 14 | &line, 15 | Stroke::new(1.5, Color32::RED), 16 | 10.0, 17 | 2.5, 18 | )); 19 | } 20 | 21 | black_box(v); 22 | }); 23 | }); 24 | } 25 | 26 | fn many_dashed_lines(c: &mut Criterion) { 27 | c.bench_function("many_dashed_lines", move |b| { 28 | b.iter(|| { 29 | let mut v = Vec::new(); 30 | 31 | let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; 32 | 33 | for _ in 0..100 { 34 | Shape::dashed_line_many(&line, Stroke::new(1.5, Color32::RED), 10.0, 2.5, &mut v); 35 | } 36 | 37 | black_box(v); 38 | }); 39 | }); 40 | } 41 | 42 | fn tessellate_circles(c: &mut Criterion) { 43 | c.bench_function("tessellate_circles_100k", move |b| { 44 | let radii: [f32; 10] = [1.0, 2.0, 3.6, 4.0, 5.7, 8.0, 10.0, 13.0, 15.0, 17.0]; 45 | let mut clipped_shapes = vec![]; 46 | for r in radii { 47 | for _ in 0..10_000 { 48 | let clip_rect = Rect::from_min_size(Pos2::ZERO, Vec2::splat(1024.0)); 49 | let shape = Shape::circle_filled(Pos2::new(10.0, 10.0), r, Color32::WHITE); 50 | clipped_shapes.push(ClippedShape(clip_rect, shape)); 51 | } 52 | } 53 | assert_eq!(clipped_shapes.len(), 100_000); 54 | 55 | let pixels_per_point = 2.0; 56 | let options = TessellationOptions::default(); 57 | 58 | let atlas = TextureAtlas::new([4096, 256]); 59 | let font_tex_size = atlas.size(); 60 | let prepared_discs = atlas.prepared_discs(); 61 | 62 | b.iter(|| { 63 | let clipped_primitive = tessellate_shapes( 64 | pixels_per_point, 65 | options, 66 | font_tex_size, 67 | prepared_discs.clone(), 68 | clipped_shapes.clone(), 69 | ); 70 | black_box(clipped_primitive); 71 | }); 72 | }); 73 | } 74 | 75 | criterion_group!( 76 | benches, 77 | single_dashed_lines, 78 | many_dashed_lines, 79 | tessellate_circles 80 | ); 81 | criterion_main!(benches); 82 | -------------------------------------------------------------------------------- /crates/epaint/fonts/Hack-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoothin/RustClock/a2dcc767b7d0aa2c6005136f22db3624eaaf367e/crates/epaint/fonts/Hack-Regular.ttf -------------------------------------------------------------------------------- /crates/epaint/fonts/Hack-Regular.txt: -------------------------------------------------------------------------------- 1 | The work in the Hack project is Copyright 2018 Source Foundry Authors and licensed under the MIT License 2 | 3 | The work in the DejaVu project was committed to the public domain. 4 | 5 | Bitstream Vera Sans Mono Copyright 2003 Bitstream Inc. and licensed under the Bitstream Vera License with Reserved Font Names "Bitstream" and "Vera" 6 | MIT License 7 | 8 | Copyright (c) 2018 Source Foundry Authors 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | BITSTREAM VERA LICENSE 16 | 17 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: 20 | 21 | The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. 22 | 23 | The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". 24 | 25 | This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. 26 | 27 | The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. 28 | 29 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. 30 | 31 | Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. 32 | -------------------------------------------------------------------------------- /crates/epaint/fonts/NotoEmoji-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoothin/RustClock/a2dcc767b7d0aa2c6005136f22db3624eaaf367e/crates/epaint/fonts/NotoEmoji-Regular.ttf -------------------------------------------------------------------------------- /crates/epaint/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | This Font Software is licensed under the SIL Open Font License, 2 | Version 1.1. 3 | 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide 13 | development of collaborative font projects, to support the font 14 | creation efforts of academic and linguistic communities, and to 15 | provide a free and open framework in which fonts may be shared and 16 | improved in partnership with others. 17 | 18 | The OFL allows the licensed fonts to be used, studied, modified and 19 | redistributed freely as long as they are not sold by themselves. The 20 | fonts, including any derivative works, can be bundled, embedded, 21 | redistributed and/or sold with any software provided that any reserved 22 | names are not used by derivative works. The fonts and derivatives, 23 | however, cannot be released under any other type of license. The 24 | requirement for fonts to remain under this license does not apply to 25 | any document created using the fonts or their derivatives. 26 | 27 | DEFINITIONS 28 | "Font Software" refers to the set of files released by the Copyright 29 | Holder(s) under this license and clearly marked as such. This may 30 | include source files, build scripts and documentation. 31 | 32 | "Reserved Font Name" refers to any names specified as such after the 33 | copyright statement(s). 34 | 35 | "Original Version" refers to the collection of Font Software 36 | components as distributed by the Copyright Holder(s). 37 | 38 | "Modified Version" refers to any derivative made by adding to, 39 | deleting, or substituting -- in part or in whole -- any of the 40 | components of the Original Version, by changing formats or by porting 41 | the Font Software to a new environment. 42 | 43 | "Author" refers to any designer, engineer, programmer, technical 44 | writer or other person who contributed to the Font Software. 45 | 46 | PERMISSION & CONDITIONS 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of the Font Software, to use, study, copy, merge, embed, 49 | modify, redistribute, and sell modified and unmodified copies of the 50 | Font Software, subject to the following conditions: 51 | 52 | 1) Neither the Font Software nor any of its individual components, in 53 | Original or Modified Versions, may be sold by itself. 54 | 55 | 2) Original or Modified Versions of the Font Software may be bundled, 56 | redistributed and/or sold with any software, provided that each copy 57 | contains the above copyright notice and this license. These can be 58 | included either as stand-alone text files, human-readable headers or 59 | in the appropriate machine-readable metadata fields within text or 60 | binary files as long as those fields can be easily viewed by the user. 61 | 62 | 3) No Modified Version of the Font Software may use the Reserved Font 63 | Name(s) unless explicit written permission is granted by the 64 | corresponding Copyright Holder. This restriction only applies to the 65 | primary font name as presented to the users. 66 | 67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 68 | Software shall not be used to promote, endorse or advertise any 69 | Modified Version, except to acknowledge the contribution(s) of the 70 | Copyright Holder(s) and the Author(s) or with their explicit written 71 | permission. 72 | 73 | 5) The Font Software, modified or unmodified, in part or in whole, 74 | must be distributed entirely under this license, and must not be 75 | distributed under any other license. The requirement for fonts to 76 | remain under this license does not apply to any document created using 77 | the Font Software. 78 | 79 | TERMINATION 80 | This license becomes null and void if any of the above conditions are 81 | not met. 82 | 83 | DISCLAIMER 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | -------------------------------------------------------------------------------- /crates/epaint/fonts/Ubuntu-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoothin/RustClock/a2dcc767b7d0aa2c6005136f22db3624eaaf367e/crates/epaint/fonts/Ubuntu-Light.ttf -------------------------------------------------------------------------------- /crates/epaint/fonts/emoji-icon-font-mit-license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 John Slegers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /crates/epaint/fonts/emoji-icon-font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoothin/RustClock/a2dcc767b7d0aa2c6005136f22db3624eaaf367e/crates/epaint/fonts/emoji-icon-font.ttf -------------------------------------------------------------------------------- /crates/epaint/fonts/list_fonts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fontTools.ttLib import TTFont 3 | from fontTools.unicode import Unicode 4 | from itertools import chain 5 | import sys 6 | 7 | ttf = TTFont(sys.argv[1], 0, verbose=0, allowVID=0, 8 | ignoreDecompileErrors=True, 9 | fontNumber=-1) 10 | 11 | chars = chain.from_iterable([y + (Unicode[y[0]],) 12 | for y in x.cmap.items()] for x in ttf["cmap"].tables) 13 | 14 | 15 | all_codepoints = {} 16 | 17 | for entry in chars: 18 | codepoint = entry[0] 19 | short_name = entry[1] 20 | long_name = entry[2].lower() 21 | if False: 22 | print(f'(0x{codepoint:02X}, "{short_name}", "{long_name}"),') 23 | else: 24 | name = short_name if long_name == "????" else long_name 25 | # print(f'(0x{codepoint:02X}, "{name}"),') 26 | all_codepoints[codepoint] = name 27 | 28 | for codepoint in sorted(all_codepoints.keys()): 29 | name = all_codepoints[codepoint] 30 | print(f'(0x{codepoint:02X}, \'{chr(codepoint)}\', "{name}"),') 31 | 32 | ttf.close() 33 | -------------------------------------------------------------------------------- /crates/epaint/src/shadow.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// The color and fuzziness of a fuzzy shape. 4 | /// Can be used for a rectangular shadow with a soft penumbra. 5 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 6 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 7 | pub struct Shadow { 8 | /// The shadow extends this much outside the rect. 9 | /// The size of the fuzzy penumbra. 10 | pub extrusion: f32, 11 | 12 | /// Color of the opaque center of the shadow. 13 | pub color: Color32, 14 | } 15 | 16 | impl Shadow { 17 | pub const NONE: Self = Self { 18 | extrusion: 0.0, 19 | color: Color32::TRANSPARENT, 20 | }; 21 | 22 | /// Tooltips, menus, …, for dark mode. 23 | pub fn small_dark() -> Self { 24 | Self { 25 | extrusion: 16.0, 26 | color: Color32::from_black_alpha(96), 27 | } 28 | } 29 | 30 | /// Tooltips, menus, …, for light mode. 31 | pub fn small_light() -> Self { 32 | Self { 33 | extrusion: 16.0, 34 | color: Color32::from_black_alpha(20), 35 | } 36 | } 37 | 38 | /// Used for egui windows in dark mode. 39 | pub fn big_dark() -> Self { 40 | Self { 41 | extrusion: 32.0, 42 | color: Color32::from_black_alpha(96), 43 | } 44 | } 45 | 46 | /// Used for egui windows in light mode. 47 | pub fn big_light() -> Self { 48 | Self { 49 | extrusion: 32.0, 50 | color: Color32::from_black_alpha(16), 51 | } 52 | } 53 | 54 | pub fn tessellate(&self, rect: Rect, rounding: impl Into) -> Mesh { 55 | // tessellator.clip_rect = clip_rect; // TODO(emilk): culling 56 | 57 | let Self { extrusion, color } = *self; 58 | 59 | let rounding: Rounding = rounding.into(); 60 | let half_ext = 0.5 * extrusion; 61 | 62 | let ext_rounding = Rounding { 63 | nw: rounding.nw + half_ext, 64 | ne: rounding.ne + half_ext, 65 | sw: rounding.sw + half_ext, 66 | se: rounding.se + half_ext, 67 | }; 68 | 69 | use crate::tessellator::*; 70 | let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color); 71 | let pixels_per_point = 1.0; // doesn't matter here 72 | let font_tex_size = [1; 2]; // unused size we are not tessellating text. 73 | let mut tessellator = Tessellator::new( 74 | pixels_per_point, 75 | TessellationOptions { 76 | feathering: true, 77 | feathering_size_in_pixels: extrusion * pixels_per_point, 78 | ..Default::default() 79 | }, 80 | font_tex_size, 81 | vec![], 82 | ); 83 | let mut mesh = Mesh::default(); 84 | tessellator.tessellate_rect(&rect, &mut mesh); 85 | mesh 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /crates/epaint/src/shape_transform.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { 4 | #![allow(clippy::match_same_arms)] 5 | match shape { 6 | Shape::Noop => {} 7 | Shape::Vec(shapes) => { 8 | for shape in shapes { 9 | adjust_colors(shape, adjust_color); 10 | } 11 | } 12 | Shape::Circle(circle_shape) => { 13 | adjust_color(&mut circle_shape.fill); 14 | adjust_color(&mut circle_shape.stroke.color); 15 | } 16 | Shape::LineSegment { stroke, .. } => { 17 | adjust_color(&mut stroke.color); 18 | } 19 | Shape::Path(path_shape) => { 20 | adjust_color(&mut path_shape.fill); 21 | adjust_color(&mut path_shape.stroke.color); 22 | } 23 | Shape::Rect(rect_shape) => { 24 | adjust_color(&mut rect_shape.fill); 25 | adjust_color(&mut rect_shape.stroke.color); 26 | } 27 | Shape::Text(text_shape) => { 28 | if let Some(override_text_color) = &mut text_shape.override_text_color { 29 | adjust_color(override_text_color); 30 | } 31 | 32 | if !text_shape.galley.is_empty() { 33 | let galley = std::sync::Arc::make_mut(&mut text_shape.galley); 34 | for row in &mut galley.rows { 35 | for vertex in &mut row.visuals.mesh.vertices { 36 | adjust_color(&mut vertex.color); 37 | } 38 | } 39 | } 40 | } 41 | Shape::Mesh(mesh) => { 42 | for v in &mut mesh.vertices { 43 | adjust_color(&mut v.color); 44 | } 45 | } 46 | Shape::QuadraticBezier(quatratic) => { 47 | adjust_color(&mut quatratic.fill); 48 | adjust_color(&mut quatratic.stroke.color); 49 | } 50 | Shape::CubicBezier(bezier) => { 51 | adjust_color(&mut bezier.fill); 52 | adjust_color(&mut bezier.stroke.color); 53 | } 54 | Shape::Callback(_) => { 55 | // Can't tint user callback code 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/epaint/src/stroke.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::derive_hash_xor_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine 2 | 3 | use super::*; 4 | 5 | /// Describes the width and color of a line. 6 | /// 7 | /// The default stroke is the same as [`Stroke::none`]. 8 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 9 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 10 | pub struct Stroke { 11 | pub width: f32, 12 | pub color: Color32, 13 | } 14 | 15 | impl Stroke { 16 | /// Same as [`Stroke::default`]. 17 | pub const NONE: Stroke = Stroke { 18 | width: 0.0, 19 | color: Color32::TRANSPARENT, 20 | }; 21 | 22 | #[deprecated = "Use Stroke::NONE instead"] 23 | #[inline(always)] 24 | pub fn none() -> Self { 25 | Self::new(0.0, Color32::TRANSPARENT) 26 | } 27 | 28 | #[inline] 29 | pub fn new(width: impl Into, color: impl Into) -> Self { 30 | Self { 31 | width: width.into(), 32 | color: color.into(), 33 | } 34 | } 35 | 36 | /// True if width is zero or color is transparent 37 | #[inline] 38 | pub fn is_empty(&self) -> bool { 39 | self.width <= 0.0 || self.color == Color32::TRANSPARENT 40 | } 41 | } 42 | 43 | impl From<(f32, Color)> for Stroke 44 | where 45 | Color: Into, 46 | { 47 | #[inline(always)] 48 | fn from((width, color): (f32, Color)) -> Stroke { 49 | Stroke::new(width, color) 50 | } 51 | } 52 | 53 | impl std::hash::Hash for Stroke { 54 | #[inline(always)] 55 | fn hash(&self, state: &mut H) { 56 | let Self { width, color } = *self; 57 | crate::f32_hash(state, width); 58 | color.hash(state); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/epaint/src/text/cursor.rs: -------------------------------------------------------------------------------- 1 | //! Different types of text cursors, i.e. ways to point into a [`super::Galley`]. 2 | 3 | /// Character cursor 4 | #[derive(Clone, Copy, Debug, Default)] 5 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 6 | pub struct CCursor { 7 | /// Character offset (NOT byte offset!). 8 | pub index: usize, 9 | 10 | /// If this cursors sits right at the border of a wrapped row break (NOT paragraph break) 11 | /// do we prefer the next row? 12 | /// This is *almost* always what you want, *except* for when 13 | /// explicitly clicking the end of a row or pressing the end key. 14 | pub prefer_next_row: bool, 15 | } 16 | 17 | impl CCursor { 18 | pub fn new(index: usize) -> Self { 19 | Self { 20 | index, 21 | prefer_next_row: false, 22 | } 23 | } 24 | } 25 | 26 | /// Two `CCursor`s are considered equal if they refer to the same character boundary, 27 | /// even if one prefers the start of the next row. 28 | impl PartialEq for CCursor { 29 | fn eq(&self, other: &CCursor) -> bool { 30 | self.index == other.index 31 | } 32 | } 33 | 34 | impl std::ops::Add for CCursor { 35 | type Output = CCursor; 36 | 37 | fn add(self, rhs: usize) -> Self::Output { 38 | CCursor { 39 | index: self.index.saturating_add(rhs), 40 | prefer_next_row: self.prefer_next_row, 41 | } 42 | } 43 | } 44 | 45 | impl std::ops::Sub for CCursor { 46 | type Output = CCursor; 47 | 48 | fn sub(self, rhs: usize) -> Self::Output { 49 | CCursor { 50 | index: self.index.saturating_sub(rhs), 51 | prefer_next_row: self.prefer_next_row, 52 | } 53 | } 54 | } 55 | 56 | impl std::ops::AddAssign for CCursor { 57 | fn add_assign(&mut self, rhs: usize) { 58 | self.index = self.index.saturating_add(rhs); 59 | } 60 | } 61 | 62 | impl std::ops::SubAssign for CCursor { 63 | fn sub_assign(&mut self, rhs: usize) { 64 | self.index = self.index.saturating_sub(rhs); 65 | } 66 | } 67 | 68 | /// Row Cursor 69 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 70 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 71 | pub struct RCursor { 72 | /// 0 is first row, and so on. 73 | /// Note that a single paragraph can span multiple rows. 74 | /// (a paragraph is text separated by `\n`). 75 | pub row: usize, 76 | 77 | /// Character based (NOT bytes). 78 | /// It is fine if this points to something beyond the end of the current row. 79 | /// When moving up/down it may again be within the next row. 80 | pub column: usize, 81 | } 82 | 83 | /// Paragraph Cursor 84 | #[derive(Clone, Copy, Debug, Default)] 85 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 86 | pub struct PCursor { 87 | /// 0 is first paragraph, and so on. 88 | /// Note that a single paragraph can span multiple rows. 89 | /// (a paragraph is text separated by `\n`). 90 | pub paragraph: usize, 91 | 92 | /// Character based (NOT bytes). 93 | /// It is fine if this points to something beyond the end of the current paragraph. 94 | /// When moving up/down it may again be within the next paragraph. 95 | pub offset: usize, 96 | 97 | /// If this cursors sits right at the border of a wrapped row break (NOT paragraph break) 98 | /// do we prefer the next row? 99 | /// This is *almost* always what you want, *except* for when 100 | /// explicitly clicking the end of a row or pressing the end key. 101 | pub prefer_next_row: bool, 102 | } 103 | 104 | /// Two `PCursor`s are considered equal if they refer to the same character boundary, 105 | /// even if one prefers the start of the next row. 106 | impl PartialEq for PCursor { 107 | fn eq(&self, other: &PCursor) -> bool { 108 | self.paragraph == other.paragraph && self.offset == other.offset 109 | } 110 | } 111 | 112 | /// All different types of cursors together. 113 | /// They all point to the same place, but in their own different ways. 114 | /// pcursor/rcursor can also point to after the end of the paragraph/row. 115 | /// Does not implement `PartialEq` because you must think which cursor should be equivalent. 116 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 117 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 118 | pub struct Cursor { 119 | pub ccursor: CCursor, 120 | pub rcursor: RCursor, 121 | pub pcursor: PCursor, 122 | } 123 | -------------------------------------------------------------------------------- /crates/epaint/src/text/mod.rs: -------------------------------------------------------------------------------- 1 | //! Everything related to text, fonts, text layout, cursors etc. 2 | 3 | pub mod cursor; 4 | mod font; 5 | mod fonts; 6 | mod text_layout; 7 | mod text_layout_types; 8 | 9 | /// One `\t` character is this many spaces wide. 10 | pub const TAB_SIZE: usize = 4; 11 | 12 | pub use { 13 | fonts::{FontData, FontDefinitions, FontFamily, FontId, FontTweak, Fonts, FontsImpl}, 14 | text_layout::layout, 15 | text_layout_types::*, 16 | }; 17 | 18 | /// Suggested character to use to replace those in password text fields. 19 | pub const PASSWORD_REPLACEMENT_CHAR: char = '•'; 20 | -------------------------------------------------------------------------------- /crates/epaint/src/texture_handle.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::{ 4 | emath::NumExt, mutex::RwLock, textures::TextureOptions, ImageData, ImageDelta, TextureId, 5 | TextureManager, 6 | }; 7 | 8 | /// Used to paint images. 9 | /// 10 | /// An _image_ is pixels stored in RAM, and represented using [`ImageData`]. 11 | /// Before you can paint it however, you need to convert it to a _texture_. 12 | /// 13 | /// If you are using egui, use `egui::Context::load_texture`. 14 | /// 15 | /// The [`TextureHandle`] can be cloned cheaply. 16 | /// When the last [`TextureHandle`] for specific texture is dropped, the texture is freed. 17 | /// 18 | /// See also [`TextureManager`]. 19 | #[must_use] 20 | pub struct TextureHandle { 21 | tex_mngr: Arc>, 22 | id: TextureId, 23 | } 24 | 25 | impl Drop for TextureHandle { 26 | fn drop(&mut self) { 27 | self.tex_mngr.write().free(self.id); 28 | } 29 | } 30 | 31 | impl Clone for TextureHandle { 32 | fn clone(&self) -> Self { 33 | self.tex_mngr.write().retain(self.id); 34 | Self { 35 | tex_mngr: self.tex_mngr.clone(), 36 | id: self.id, 37 | } 38 | } 39 | } 40 | 41 | impl PartialEq for TextureHandle { 42 | #[inline] 43 | fn eq(&self, other: &Self) -> bool { 44 | self.id == other.id 45 | } 46 | } 47 | 48 | impl Eq for TextureHandle {} 49 | 50 | impl std::hash::Hash for TextureHandle { 51 | #[inline] 52 | fn hash(&self, state: &mut H) { 53 | self.id.hash(state); 54 | } 55 | } 56 | 57 | impl TextureHandle { 58 | /// If you are using egui, use `egui::Context::load_texture` instead. 59 | pub fn new(tex_mngr: Arc>, id: TextureId) -> Self { 60 | Self { tex_mngr, id } 61 | } 62 | 63 | #[inline] 64 | pub fn id(&self) -> TextureId { 65 | self.id 66 | } 67 | 68 | /// Assign a new image to an existing texture. 69 | pub fn set(&mut self, image: impl Into, options: TextureOptions) { 70 | self.tex_mngr 71 | .write() 72 | .set(self.id, ImageDelta::full(image.into(), options)); 73 | } 74 | 75 | /// Assign a new image to a subregion of the whole texture. 76 | pub fn set_partial( 77 | &mut self, 78 | pos: [usize; 2], 79 | image: impl Into, 80 | options: TextureOptions, 81 | ) { 82 | self.tex_mngr 83 | .write() 84 | .set(self.id, ImageDelta::partial(pos, image.into(), options)); 85 | } 86 | 87 | /// width x height 88 | pub fn size(&self) -> [usize; 2] { 89 | self.tex_mngr.read().meta(self.id).unwrap().size 90 | } 91 | 92 | /// width x height 93 | pub fn size_vec2(&self) -> crate::Vec2 { 94 | let [w, h] = self.size(); 95 | crate::Vec2::new(w as f32, h as f32) 96 | } 97 | 98 | /// width / height 99 | pub fn aspect_ratio(&self) -> f32 { 100 | let [w, h] = self.size(); 101 | w as f32 / h.at_least(1) as f32 102 | } 103 | 104 | /// Debug-name. 105 | pub fn name(&self) -> String { 106 | self.tex_mngr.read().meta(self.id).unwrap().name.clone() 107 | } 108 | } 109 | 110 | impl From<&TextureHandle> for TextureId { 111 | #[inline(always)] 112 | fn from(handle: &TextureHandle) -> Self { 113 | handle.id() 114 | } 115 | } 116 | 117 | impl From<&mut TextureHandle> for TextureId { 118 | #[inline(always)] 119 | fn from(handle: &mut TextureHandle) -> Self { 120 | handle.id() 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/epaint/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | mod ordered_float; 2 | 3 | pub use ordered_float::*; 4 | 5 | /// Hash the given value with a predictable hasher. 6 | #[inline] 7 | pub fn hash(value: impl std::hash::Hash) -> u64 { 8 | ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(value) 9 | } 10 | 11 | /// Hash the given value with the given hasher. 12 | #[inline] 13 | pub fn hash_with(value: impl std::hash::Hash, mut hasher: impl std::hash::Hasher) -> u64 { 14 | value.hash(&mut hasher); 15 | hasher.finish() 16 | } 17 | -------------------------------------------------------------------------------- /crates/epaint/src/util/ordered_float.rs: -------------------------------------------------------------------------------- 1 | //! Total order on floating point types. 2 | //! Can be used for sorting, min/max computation, and other collection algorithms. 3 | 4 | use std::cmp::Ordering; 5 | use std::hash::{Hash, Hasher}; 6 | 7 | /// Wraps a floating-point value to add total order and hash. 8 | /// Possible types for `T` are `f32` and `f64`. 9 | /// 10 | /// See also [`FloatOrd`]. 11 | pub struct OrderedFloat(T); 12 | 13 | impl Eq for OrderedFloat {} 14 | 15 | impl PartialEq for OrderedFloat { 16 | #[inline] 17 | fn eq(&self, other: &Self) -> bool { 18 | // NaNs are considered equal (equivalent) when it comes to ordering 19 | if self.0.is_nan() { 20 | other.0.is_nan() 21 | } else { 22 | self.0 == other.0 23 | } 24 | } 25 | } 26 | 27 | impl PartialOrd for OrderedFloat { 28 | #[inline] 29 | fn partial_cmp(&self, other: &Self) -> Option { 30 | match self.0.partial_cmp(&other.0) { 31 | Some(ord) => Some(ord), 32 | None => Some(self.0.is_nan().cmp(&other.0.is_nan())), 33 | } 34 | } 35 | } 36 | 37 | impl Ord for OrderedFloat { 38 | #[inline] 39 | fn cmp(&self, other: &Self) -> Ordering { 40 | match self.partial_cmp(other) { 41 | Some(ord) => ord, 42 | None => unreachable!(), 43 | } 44 | } 45 | } 46 | 47 | impl Hash for OrderedFloat { 48 | fn hash(&self, state: &mut H) { 49 | self.0.hash(state); 50 | } 51 | } 52 | 53 | // ---------------------------------------------------------------------------- 54 | 55 | /// Extension trait to provide `ord()` method. 56 | /// 57 | /// Example with `f64`: 58 | /// ``` 59 | /// use epaint::util::FloatOrd; 60 | /// 61 | /// let array = [1.0, 2.5, 2.0]; 62 | /// let max = array.iter().max_by_key(|val| val.ord()); 63 | /// 64 | /// assert_eq!(max, Some(&2.5)); 65 | /// ``` 66 | pub trait FloatOrd { 67 | /// Type to provide total order, useful as key in sorted contexts. 68 | fn ord(self) -> OrderedFloat 69 | where 70 | Self: Sized; 71 | } 72 | 73 | impl FloatOrd for f32 { 74 | #[inline] 75 | fn ord(self) -> OrderedFloat { 76 | OrderedFloat(self) 77 | } 78 | } 79 | 80 | impl FloatOrd for f64 { 81 | #[inline] 82 | fn ord(self) -> OrderedFloat { 83 | OrderedFloat(self) 84 | } 85 | } 86 | 87 | // ---------------------------------------------------------------------------- 88 | 89 | /// Internal abstraction over floating point types 90 | #[doc(hidden)] 91 | pub trait Float: PartialOrd + PartialEq + private::FloatImpl {} 92 | 93 | impl Float for f32 {} 94 | 95 | impl Float for f64 {} 96 | 97 | // Keep this trait in private module, to avoid exposing its methods as extensions in user code 98 | mod private { 99 | use super::*; 100 | 101 | pub trait FloatImpl { 102 | fn is_nan(&self) -> bool; 103 | 104 | fn hash(&self, state: &mut H); 105 | } 106 | 107 | impl FloatImpl for f32 { 108 | #[inline] 109 | fn is_nan(&self) -> bool { 110 | f32::is_nan(*self) 111 | } 112 | 113 | #[inline] 114 | fn hash(&self, state: &mut H) { 115 | crate::f32_hash(state, *self); 116 | } 117 | } 118 | 119 | impl FloatImpl for f64 { 120 | #[inline] 121 | fn is_nan(&self) -> bool { 122 | f64::is_nan(*self) 123 | } 124 | 125 | #[inline] 126 | fn hash(&self, state: &mut H) { 127 | crate::f64_hash(state, *self); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoothin/RustClock/a2dcc767b7d0aa2c6005136f22db3624eaaf367e/icon.ico -------------------------------------------------------------------------------- /pic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoothin/RustClock/a2dcc767b7d0aa2c6005136f22db3624eaaf367e/pic.gif --------------------------------------------------------------------------------