commit
4c9a4896af
25 changed files with 4584 additions and 294 deletions
|
@ -10,6 +10,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
### Added ⭐
|
||||
|
||||
* Emoji support:
|
||||
* 1216 different emojis.
|
||||
* Works in any text.
|
||||
* Great for button icons.
|
||||
* The Demo app comes with a Font Book to explore the available glyphs.
|
||||
* Wrapping layouts:
|
||||
* `ui.horizontal_wrapped(|ui| ...)`: Add widgets on a row but wrap at `max_size`.
|
||||
* `ui.horizontal_wrapped_for_text`: Like `horizontal_wrapped`, but with spacing made for embedding text.
|
||||
|
@ -22,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Changed 🔧
|
||||
|
||||
* Changed default font to [Ubuntu-Light](https://fonts.google.com/specimen/Ubuntu).
|
||||
* Remove minimum button width
|
||||
* Refactored `egui::Layout` substantially, changing its interface.
|
||||
|
||||
### Removed 🔥
|
||||
|
|
|
@ -209,5 +209,7 @@ Egui is under MIT OR Apache-2.0 license.
|
|||
|
||||
Fonts:
|
||||
|
||||
* ProggyClean.ttf, Copyright (c) 2004, 2005 Tristan Grimmer. MIT License. <http://www.proggyfonts.net/>
|
||||
* Ubuntu-Light.ttf by [Dalton Maag](http://www.daltonmaag.com/): [Ubuntu font licence](https://ubuntu.com/legal/font-licence)
|
||||
* `emoji-icon-font.ttf`: [Copyright (c) 2014 John Slegers](https://github.com/jslegers/emoji-icon-font) , MIT License
|
||||
* `NotoEmoji-Regular.ttf`: [google.com/get/noto](https://google.com/get/noto), [SIL Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL)
|
||||
* `ProggyClean.ttf`: Copyright (c) 2004, 2005 Tristan Grimmer. MIT License. <http://www.proggyfonts.net/>
|
||||
* `Ubuntu-Light.ttf` by [Dalton Maag](http://www.daltonmaag.com/): [Ubuntu font licence](https://ubuntu.com/legal/font-licence)
|
||||
|
|
4
TODO.md
4
TODO.md
|
@ -21,8 +21,6 @@ TODO-list for the Egui project. If you looking for something to do, look here.
|
|||
* [/] Unicode
|
||||
* [/] Text editing of unicode (needs more testing)
|
||||
* [ ] Font with some more unicode characters
|
||||
* [ ] Emoji support (great for things like ▶️⏸⏹⚠︎)
|
||||
* [ ] Change text style/color and continue in same layout
|
||||
* Menu bar (File, Edit, etc)
|
||||
* [ ] Sub-menus
|
||||
* [ ] Keyboard shortcuts
|
||||
|
@ -117,6 +115,8 @@ Ability to do a search for any widget. The search works even for collapsed regio
|
|||
|
||||
* Widgets
|
||||
* [x] Label
|
||||
* [x] Emoji support (great for things like ▶️⏸⏹⚠︎)
|
||||
* [x] Change text style/color and continue in same layout
|
||||
* [x] Button
|
||||
* [x] Checkbox
|
||||
* [x] Radiobutton
|
||||
|
|
BIN
egui/fonts/NotoEmoji-Regular.ttf
Normal file
BIN
egui/fonts/NotoEmoji-Regular.ttf
Normal file
Binary file not shown.
185
OFL.txt → egui/fonts/OFL.txt
Executable file → Normal file
185
OFL.txt → egui/fonts/OFL.txt
Executable file → Normal file
|
@ -1,93 +1,92 @@
|
|||
Copyright 2011 The Comfortaa Project Authors (https://github.com/alexeiva/comfortaa), with Reserved Font Name "Comfortaa".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
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 THE
|
||||
COPYRIGHT HOLDER 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.
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
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 THE
|
||||
COPYRIGHT HOLDER 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.
|
9
egui/fonts/emoji-icon-font-mit-license.txt
Normal file
9
egui/fonts/emoji-icon-font-mit-license.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2014 John Slegers
|
||||
|
||||
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:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
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.
|
BIN
egui/fonts/emoji-icon-font.ttf
Normal file
BIN
egui/fonts/emoji-icon-font.ttf
Normal file
Binary file not shown.
|
@ -587,13 +587,13 @@ impl Context {
|
|||
pub fn settings_ui(&self, ui: &mut Ui) {
|
||||
use crate::containers::*;
|
||||
|
||||
CollapsingHeader::new("Style")
|
||||
CollapsingHeader::new("🎑 Style")
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
self.style_ui(ui);
|
||||
});
|
||||
|
||||
CollapsingHeader::new("Fonts")
|
||||
CollapsingHeader::new("🔠 Fonts")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
let mut font_definitions = self.fonts().definitions().clone();
|
||||
|
@ -602,7 +602,7 @@ impl Context {
|
|||
self.set_fonts(font_definitions);
|
||||
});
|
||||
|
||||
CollapsingHeader::new("Painting")
|
||||
CollapsingHeader::new("✒ Painting")
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
let mut tesselation_options = self.options.lock().tesselation_options;
|
||||
|
@ -625,11 +625,11 @@ impl Context {
|
|||
.on_hover_text("Is Egui currently listening for text input");
|
||||
ui.advance_cursor(16.0);
|
||||
|
||||
CollapsingHeader::new("Input")
|
||||
CollapsingHeader::new("📥 Input")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| ui.input().clone().ui(ui));
|
||||
|
||||
CollapsingHeader::new("Paint stats")
|
||||
CollapsingHeader::new("📊 Paint stats")
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
self.paint_stats.lock().ui(ui);
|
||||
|
|
|
@ -89,7 +89,7 @@ impl FrameHistory {
|
|||
);
|
||||
crate::demos::warn_if_debug_build(ui);
|
||||
|
||||
crate::CollapsingHeader::new("CPU usage history")
|
||||
crate::CollapsingHeader::new("📊 CPU usage history")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
self.graph(ui);
|
||||
|
@ -304,7 +304,7 @@ impl app::App for DemoApp {
|
|||
&mut integration_context.tex_allocator,
|
||||
);
|
||||
|
||||
crate::Window::new("Backend")
|
||||
crate::Window::new("💻 Backend")
|
||||
.min_width(360.0)
|
||||
.scroll(false)
|
||||
.show(ctx, |ui| {
|
||||
|
|
|
@ -14,7 +14,7 @@ impl Default for DancingStrings {
|
|||
|
||||
impl Demo for DancingStrings {
|
||||
fn name(&self) -> &str {
|
||||
"Dancing Strings"
|
||||
"♫ Dancing Strings"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &Arc<Context>, open: &mut bool) {
|
||||
|
|
|
@ -37,6 +37,7 @@ impl Default for Demos {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
demos: vec![
|
||||
(false, Box::new(crate::demos::FontBook::default())),
|
||||
(false, Box::new(crate::demos::DancingStrings::default())),
|
||||
(false, Box::new(crate::demos::DragAndDropDemo::default())),
|
||||
(false, Box::new(crate::demos::Tests::default())),
|
||||
|
@ -103,10 +104,12 @@ impl DemoWindows {
|
|||
}
|
||||
|
||||
crate::SidePanel::left(Id::new("side_panel"), 200.0).show(ctx, |ui| {
|
||||
ui.heading("Egui Demo");
|
||||
ui.heading("✒ Egui Demo");
|
||||
crate::demos::warn_if_debug_build(ui);
|
||||
ui.label("Egui is an immediate mode GUI library written in Rust.");
|
||||
ui.add(crate::Hyperlink::new("https://github.com/emilk/egui").text("Egui home page"));
|
||||
ui.add(crate::Hyperlink::new("https://github.com/emilk/egui").text(" Egui home page"));
|
||||
|
||||
ui.label("Egui can be run on the web, or natively on 🐧");
|
||||
|
||||
ui.separator();
|
||||
ui.label(
|
||||
|
@ -117,7 +120,7 @@ impl DemoWindows {
|
|||
}
|
||||
ui.separator();
|
||||
|
||||
ui.heading("Windows:");
|
||||
ui.heading("S Windows:");
|
||||
ui.indent("windows", |ui| {
|
||||
self.open_windows.checkboxes(ui);
|
||||
self.demos.checkboxes(ui);
|
||||
|
@ -147,34 +150,34 @@ impl DemoWindows {
|
|||
..
|
||||
} = self;
|
||||
|
||||
Window::new("Demo")
|
||||
Window::new("✨ Demo")
|
||||
.open(&mut open_windows.demo)
|
||||
.scroll(true)
|
||||
.show(ctx, |ui| {
|
||||
demo_window.ui(ui);
|
||||
});
|
||||
|
||||
Window::new("Settings")
|
||||
Window::new("🔧 Settings")
|
||||
.open(&mut open_windows.settings)
|
||||
.show(ctx, |ui| {
|
||||
ctx.settings_ui(ui);
|
||||
});
|
||||
|
||||
Window::new("Inspection")
|
||||
Window::new("🔍 Inspection")
|
||||
.open(&mut open_windows.inspection)
|
||||
.scroll(true)
|
||||
.show(ctx, |ui| {
|
||||
ctx.inspection_ui(ui);
|
||||
});
|
||||
|
||||
Window::new("Memory")
|
||||
Window::new("📝 Memory")
|
||||
.open(&mut open_windows.memory)
|
||||
.resizable(false)
|
||||
.show(ctx, |ui| {
|
||||
ctx.memory_ui(ui);
|
||||
});
|
||||
|
||||
Window::new("Color Test")
|
||||
Window::new("🎨 Color Test")
|
||||
.default_size([800.0, 1024.0])
|
||||
.scroll(true)
|
||||
.open(&mut open_windows.color_test)
|
||||
|
@ -299,18 +302,18 @@ impl OpenWindows {
|
|||
color_test,
|
||||
} = self;
|
||||
ui.label("Egui:");
|
||||
ui.checkbox(settings, "Settings");
|
||||
ui.checkbox(inspection, "Inspection");
|
||||
ui.checkbox(memory, "Memory");
|
||||
ui.checkbox(settings, "🔧 Settings");
|
||||
ui.checkbox(inspection, "🔍 Inspection");
|
||||
ui.checkbox(memory, "📝 Memory");
|
||||
ui.separator();
|
||||
ui.checkbox(demo, "Demo");
|
||||
ui.checkbox(demo, "✨ Demo");
|
||||
ui.separator();
|
||||
ui.checkbox(resize, "Resize examples");
|
||||
ui.checkbox(color_test, "Color test")
|
||||
ui.checkbox(resize, "↔ Resize examples");
|
||||
ui.checkbox(color_test, "🎨 Color test")
|
||||
.on_hover_text("For testing the integrations painter");
|
||||
ui.separator();
|
||||
ui.label("Misc:");
|
||||
ui.checkbox(fractal_clock, "Fractal Clock");
|
||||
ui.checkbox(fractal_clock, "🕑 Fractal Clock");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,7 +336,7 @@ fn show_menu_bar(ui: &mut Ui, windows: &mut OpenWindows, seconds_since_midnight:
|
|||
menu::menu(ui, "Windows", |ui| windows.checkboxes(ui));
|
||||
menu::menu(ui, "About", |ui| {
|
||||
ui.label("This is Egui");
|
||||
ui.add(Hyperlink::new("https://github.com/emilk/egui").text("Egui home page"));
|
||||
ui.add(Hyperlink::new("https://github.com/emilk/egui").text(" Egui home page"));
|
||||
});
|
||||
|
||||
if let Some(time) = seconds_since_midnight {
|
||||
|
|
|
@ -97,7 +97,7 @@ impl Default for DragAndDropDemo {
|
|||
|
||||
impl Demo for DragAndDropDemo {
|
||||
fn name(&self) -> &str {
|
||||
"Drag and Drop"
|
||||
"✋ Drag and Drop"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &std::sync::Arc<Context>, open: &mut bool) {
|
||||
|
|
105
egui/src/demos/font_book.rs
Normal file
105
egui/src/demos/font_book.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct FontBook {
|
||||
standard: bool,
|
||||
emojis: bool,
|
||||
filter: String,
|
||||
text_style: TextStyle,
|
||||
}
|
||||
|
||||
impl Default for FontBook {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
standard: false,
|
||||
emojis: true,
|
||||
filter: Default::default(),
|
||||
text_style: TextStyle::Heading,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FontBook {
|
||||
fn characters_ui(&self, ui: &mut Ui, characters: &[(u32, char, &str)]) {
|
||||
for &(_, chr, name) in characters {
|
||||
if !self.filter.is_empty() && !name.contains(&self.filter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let button = Button::new(chr).text_style(self.text_style).frame(false);
|
||||
|
||||
let tooltip_ui = |ui: &mut Ui| {
|
||||
ui.add(Label::new(chr).text_style(self.text_style));
|
||||
ui.label(format!("{}\nU+{:X}\n\nClick to copy", name, chr as u32));
|
||||
};
|
||||
|
||||
if ui.add(button).on_hover_ui(tooltip_ui).clicked {
|
||||
ui.output().copied_text = chr.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl demos::Demo for FontBook {
|
||||
fn name(&self) -> &str {
|
||||
"🔤 Font Book"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &std::sync::Arc<crate::Context>, open: &mut bool) {
|
||||
Window::new(self.name()).open(open).show(ctx, |ui| {
|
||||
use demos::View;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl demos::View for FontBook {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
use crate::demos::font_contents_emoji::FULL_EMOJI_LIST;
|
||||
use crate::demos::font_contents_ubuntu::UBUNTU_FONT_CHARACTERS;
|
||||
|
||||
ui.label(format!(
|
||||
"Egui supports {} standard characters and {} emojis.\nClick on a character to copy it.",
|
||||
UBUNTU_FONT_CHARACTERS.len(),
|
||||
FULL_EMOJI_LIST.len(),
|
||||
));
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Text style:");
|
||||
for style in TextStyle::all() {
|
||||
ui.radio_value(&mut self.text_style, style, format!("{:?}", style));
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Show:");
|
||||
ui.checkbox(&mut self.standard, "Standard");
|
||||
ui.checkbox(&mut self.emojis, "Emojis");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Filter:");
|
||||
ui.text_edit_singleline(&mut self.filter);
|
||||
self.filter = self.filter.to_lowercase();
|
||||
if ui.button("x").clicked {
|
||||
self.filter.clear();
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
crate::ScrollArea::auto_sized().show(ui, |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.style_mut().spacing.item_spacing = Vec2::splat(2.0);
|
||||
|
||||
if self.standard {
|
||||
self.characters_ui(ui, UBUNTU_FONT_CHARACTERS);
|
||||
}
|
||||
if self.emojis {
|
||||
self.characters_ui(ui, FULL_EMOJI_LIST);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
2754
egui/src/demos/font_contents_emoji.rs
Normal file
2754
egui/src/demos/font_contents_emoji.rs
Normal file
File diff suppressed because it is too large
Load diff
1191
egui/src/demos/font_contents_ubuntu.rs
Normal file
1191
egui/src/demos/font_contents_ubuntu.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -37,7 +37,7 @@ impl FractalClock {
|
|||
open: &mut bool,
|
||||
seconds_since_midnight: Option<f64>,
|
||||
) {
|
||||
Window::new("FractalClock")
|
||||
Window::new("🕑 Fractal Clock")
|
||||
.open(open)
|
||||
.default_size(vec2(512.0, 512.0))
|
||||
.scroll(false)
|
||||
|
|
|
@ -7,6 +7,9 @@ mod dancing_strings;
|
|||
pub mod demo_window;
|
||||
mod demo_windows;
|
||||
mod drag_and_drop;
|
||||
mod font_book;
|
||||
pub mod font_contents_emoji;
|
||||
pub mod font_contents_ubuntu;
|
||||
mod fractal_clock;
|
||||
mod sliders;
|
||||
mod tests;
|
||||
|
@ -15,8 +18,8 @@ mod widgets;
|
|||
|
||||
pub use {
|
||||
app::*, color_test::ColorTest, dancing_strings::DancingStrings, demo_window::DemoWindow,
|
||||
demo_windows::*, drag_and_drop::*, fractal_clock::FractalClock, sliders::Sliders, tests::Tests,
|
||||
widgets::Widgets,
|
||||
demo_windows::*, drag_and_drop::*, font_book::FontBook, fractal_clock::FractalClock,
|
||||
sliders::Sliders, tests::Tests, widgets::Widgets,
|
||||
};
|
||||
|
||||
pub const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
|
||||
|
@ -45,7 +48,7 @@ pub trait Demo {
|
|||
pub fn warn_if_debug_build(ui: &mut crate::Ui) {
|
||||
if crate::has_debug_assertions() {
|
||||
ui.label(
|
||||
crate::Label::new("[Debug build]")
|
||||
crate::Label::new("‼ Debug build ‼")
|
||||
.small()
|
||||
.text_color(crate::color::RED),
|
||||
)
|
||||
|
|
|
@ -5,7 +5,7 @@ pub struct Tests {}
|
|||
|
||||
impl demos::Demo for Tests {
|
||||
fn name(&self) -> &str {
|
||||
"Tests"
|
||||
"📋 Tests"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &std::sync::Arc<crate::Context>, open: &mut bool) {
|
||||
|
|
|
@ -410,7 +410,7 @@ impl InputState {
|
|||
ui.style_mut().body_text_style = crate::paint::TextStyle::Monospace;
|
||||
ui.collapsing("Raw Input", |ui| raw.ui(ui));
|
||||
|
||||
crate::containers::CollapsingHeader::new("mouse")
|
||||
crate::containers::CollapsingHeader::new("🖱 Mouse")
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
mouse.ui(ui);
|
||||
|
|
|
@ -16,9 +16,9 @@ use super::texture_atlas::TextureAtlas;
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// const REPLACEMENT_CHAR: char = '\u{25A1}'; // □ white square Replaces a missing or unsupported Unicode character.
|
||||
// const REPLACEMENT_CHAR: char = '\u{FFFD}'; // <20> REPLACEMENT CHARACTER
|
||||
const REPLACEMENT_CHAR: char = '?';
|
||||
// const REPLACEMENT_CHAR: char = '?';
|
||||
const REPLACEMENT_CHAR: char = '◻'; // white medium square
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UvRect {
|
||||
|
@ -44,57 +44,49 @@ pub struct GlyphInfo {
|
|||
pub uv_rect: Option<UvRect>,
|
||||
}
|
||||
|
||||
impl Default for GlyphInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: rusttype::GlyphId(0),
|
||||
advance_width: 0.0,
|
||||
uv_rect: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A specific font with a size.
|
||||
/// The interface uses points as the unit for everything.
|
||||
pub struct Font {
|
||||
font: rusttype::Font<'static>,
|
||||
pub struct FontImpl {
|
||||
rusttype_font: Arc<rusttype::Font<'static>>,
|
||||
/// Maximum character height
|
||||
scale_in_pixels: f32,
|
||||
pixels_per_point: f32,
|
||||
replacement_glyph_info: GlyphInfo,
|
||||
glyph_infos: RwLock<AHashMap<char, GlyphInfo>>,
|
||||
glyph_info_cache: RwLock<AHashMap<char, GlyphInfo>>, // TODO: standard Mutex
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
impl FontImpl {
|
||||
pub fn new(
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
font_data: &'static [u8],
|
||||
scale_in_points: f32,
|
||||
pixels_per_point: f32,
|
||||
) -> Font {
|
||||
rusttype_font: Arc<rusttype::Font<'static>>,
|
||||
scale_in_points: f32,
|
||||
) -> FontImpl {
|
||||
assert!(scale_in_points > 0.0);
|
||||
assert!(pixels_per_point > 0.0);
|
||||
|
||||
let font = rusttype::Font::try_from_bytes(font_data).expect("Error constructing Font");
|
||||
let scale_in_pixels = pixels_per_point * scale_in_points;
|
||||
|
||||
let replacement_glyph_info = allocate_glyph(
|
||||
&mut atlas.lock(),
|
||||
REPLACEMENT_CHAR,
|
||||
&font,
|
||||
let font = Self {
|
||||
rusttype_font,
|
||||
scale_in_pixels,
|
||||
pixels_per_point,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Failed to find replacement character {:?}",
|
||||
REPLACEMENT_CHAR
|
||||
)
|
||||
});
|
||||
|
||||
let font = Font {
|
||||
font,
|
||||
scale_in_pixels,
|
||||
pixels_per_point,
|
||||
replacement_glyph_info,
|
||||
glyph_infos: Default::default(),
|
||||
glyph_info_cache: Default::default(),
|
||||
atlas,
|
||||
};
|
||||
|
||||
font.glyph_infos
|
||||
.write()
|
||||
.insert(REPLACEMENT_CHAR, font.replacement_glyph_info);
|
||||
|
||||
// Preload the printable ASCII characters [32, 126] (which excludes control codes):
|
||||
const FIRST_ASCII: usize = 32; // 32 == space
|
||||
const LAST_ASCII: usize = 126;
|
||||
|
@ -106,8 +98,39 @@ impl Font {
|
|||
font
|
||||
}
|
||||
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
/// `\n` will result in `None`
|
||||
fn glyph_info(&self, c: char) -> Option<GlyphInfo> {
|
||||
{
|
||||
if let Some(glyph_info) = self.glyph_info_cache.read().get(&c) {
|
||||
return Some(*glyph_info);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new character:
|
||||
let glyph = self.rusttype_font.glyph(c);
|
||||
if glyph.id().0 == 0 {
|
||||
None
|
||||
} else {
|
||||
let glyph_info = allocate_glyph(
|
||||
&mut self.atlas.lock(),
|
||||
glyph,
|
||||
self.scale_in_pixels,
|
||||
self.pixels_per_point,
|
||||
);
|
||||
self.glyph_info_cache.write().insert(c, glyph_info);
|
||||
Some(glyph_info)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pair_kerning(
|
||||
&self,
|
||||
last_glyph_id: rusttype::GlyphId,
|
||||
glyph_id: rusttype::GlyphId,
|
||||
) -> f32 {
|
||||
let scale_in_pixels = Scale::uniform(self.scale_in_pixels);
|
||||
self.rusttype_font
|
||||
.pair_kerning(scale_in_pixels, last_glyph_id, glyph_id)
|
||||
/ self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Height of one row of text. In points
|
||||
|
@ -115,34 +138,121 @@ impl Font {
|
|||
self.scale_in_pixels / self.pixels_per_point
|
||||
}
|
||||
|
||||
pub fn pixels_per_point(&self) -> f32 {
|
||||
self.pixels_per_point
|
||||
}
|
||||
}
|
||||
|
||||
type FontIndex = usize;
|
||||
|
||||
// TODO: rename Layouter ?
|
||||
/// Wrapper over multiple `FontImpl` (commonly two: primary + emoji fallback)
|
||||
pub struct Font {
|
||||
fonts: Vec<Arc<FontImpl>>,
|
||||
replacement_glyph: (FontIndex, GlyphInfo),
|
||||
pixels_per_point: f32,
|
||||
row_height: f32,
|
||||
glyph_info_cache: RwLock<AHashMap<char, (FontIndex, GlyphInfo)>>,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
pub fn new(fonts: Vec<Arc<FontImpl>>) -> Self {
|
||||
assert!(!fonts.is_empty());
|
||||
let pixels_per_point = fonts[0].pixels_per_point();
|
||||
let row_height = fonts[0].row_height();
|
||||
|
||||
let mut slf = Self {
|
||||
fonts,
|
||||
replacement_glyph: Default::default(),
|
||||
pixels_per_point,
|
||||
row_height,
|
||||
glyph_info_cache: Default::default(),
|
||||
};
|
||||
let replacement_glyph = slf
|
||||
.glyph_info_no_cache(REPLACEMENT_CHAR)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Failed to find replacement character {:?}",
|
||||
REPLACEMENT_CHAR
|
||||
)
|
||||
});
|
||||
slf.replacement_glyph = replacement_glyph;
|
||||
slf
|
||||
}
|
||||
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Height of one row of text. In points
|
||||
pub fn row_height(&self) -> f32 {
|
||||
self.row_height
|
||||
}
|
||||
|
||||
pub fn uv_rect(&self, c: char) -> Option<UvRect> {
|
||||
self.glyph_infos.read().get(&c).and_then(|gi| gi.uv_rect)
|
||||
self.glyph_info_cache
|
||||
.read()
|
||||
.get(&c)
|
||||
.and_then(|gi| gi.1.uv_rect)
|
||||
}
|
||||
|
||||
pub fn glyph_width(&self, c: char) -> f32 {
|
||||
self.glyph_info(c).advance_width
|
||||
self.glyph_info(c).1.advance_width
|
||||
}
|
||||
|
||||
/// `\n` will (intentionally) show up as '?' (`REPLACEMENT_CHAR`)
|
||||
fn glyph_info(&self, c: char) -> GlyphInfo {
|
||||
/// `\n` will (intentionally) show up as `REPLACEMENT_CHAR`
|
||||
fn glyph_info(&self, c: char) -> (FontIndex, GlyphInfo) {
|
||||
{
|
||||
if let Some(glyph_info) = self.glyph_infos.read().get(&c) {
|
||||
if let Some(glyph_info) = self.glyph_info_cache.read().get(&c) {
|
||||
return *glyph_info;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new character:
|
||||
let glyph_info = allocate_glyph(
|
||||
&mut self.atlas.lock(),
|
||||
c,
|
||||
&self.font,
|
||||
self.scale_in_pixels,
|
||||
self.pixels_per_point,
|
||||
);
|
||||
// debug_assert!(glyph_info.is_some(), "Failed to find {:?}", c);
|
||||
let glyph_info = glyph_info.unwrap_or(self.replacement_glyph_info);
|
||||
self.glyph_infos.write().insert(c, glyph_info);
|
||||
glyph_info
|
||||
let font_index_glyph_info = self.glyph_info_no_cache(c);
|
||||
let font_index_glyph_info = font_index_glyph_info.unwrap_or(self.replacement_glyph);
|
||||
self.glyph_info_cache
|
||||
.write()
|
||||
.insert(c, font_index_glyph_info);
|
||||
font_index_glyph_info
|
||||
}
|
||||
|
||||
fn glyph_info_no_cache(&self, c: char) -> Option<(FontIndex, GlyphInfo)> {
|
||||
for (font_index, font_impl) in self.fonts.iter().enumerate() {
|
||||
if let Some(glyph_info) = font_impl.glyph_info(c) {
|
||||
self.glyph_info_cache
|
||||
.write()
|
||||
.insert(c, (font_index, glyph_info));
|
||||
return Some((font_index, glyph_info));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Typeset the given text onto one row.
|
||||
/// Assumes there are no `\n` in the text.
|
||||
/// Return `x_offsets`, one longer than the number of characters in the text.
|
||||
fn layout_single_row_fragment(&self, text: &str) -> Vec<f32> {
|
||||
let mut x_offsets = Vec::with_capacity(text.chars().count() + 1);
|
||||
x_offsets.push(0.0);
|
||||
|
||||
let mut cursor_x_in_points = 0.0f32;
|
||||
let mut last_glyph_id = None;
|
||||
|
||||
for c in text.chars() {
|
||||
let (font_index, glyph_info) = self.glyph_info(c);
|
||||
let font_impl = &self.fonts[font_index];
|
||||
|
||||
if let Some(last_glyph_id) = last_glyph_id {
|
||||
cursor_x_in_points += font_impl.pair_kerning(last_glyph_id, glyph_info.id)
|
||||
}
|
||||
cursor_x_in_points += glyph_info.advance_width;
|
||||
cursor_x_in_points = self.round_to_pixel(cursor_x_in_points);
|
||||
last_glyph_id = Some(glyph_info.id);
|
||||
|
||||
x_offsets.push(cursor_x_in_points);
|
||||
}
|
||||
|
||||
x_offsets
|
||||
}
|
||||
|
||||
/// Typeset the given text onto one row.
|
||||
|
@ -240,37 +350,6 @@ impl Font {
|
|||
galley
|
||||
}
|
||||
|
||||
/// Typeset the given text onto one row.
|
||||
/// Assumes there are no `\n` in the text.
|
||||
/// Return `x_offsets`, one longer than the number of characters in the text.
|
||||
fn layout_single_row_fragment(&self, text: &str) -> Vec<f32> {
|
||||
let scale_in_pixels = Scale::uniform(self.scale_in_pixels);
|
||||
|
||||
let mut x_offsets = Vec::with_capacity(text.chars().count() + 1);
|
||||
x_offsets.push(0.0);
|
||||
|
||||
let mut cursor_x_in_points = 0.0f32;
|
||||
let mut last_glyph_id = None;
|
||||
|
||||
for c in text.chars() {
|
||||
let glyph = self.glyph_info(c);
|
||||
|
||||
if let Some(last_glyph_id) = last_glyph_id {
|
||||
cursor_x_in_points +=
|
||||
self.font
|
||||
.pair_kerning(scale_in_pixels, last_glyph_id, glyph.id)
|
||||
/ self.pixels_per_point
|
||||
}
|
||||
cursor_x_in_points += glyph.advance_width;
|
||||
cursor_x_in_points = self.round_to_pixel(cursor_x_in_points);
|
||||
last_glyph_id = Some(glyph.id);
|
||||
|
||||
x_offsets.push(cursor_x_in_points);
|
||||
}
|
||||
|
||||
x_offsets
|
||||
}
|
||||
|
||||
/// A paragraph is text with no line break character in it.
|
||||
/// The text will be wrapped by the given `max_width_in_points`.
|
||||
/// Always returns at least one row.
|
||||
|
@ -366,15 +445,11 @@ impl Font {
|
|||
|
||||
fn allocate_glyph(
|
||||
atlas: &mut TextureAtlas,
|
||||
c: char,
|
||||
font: &rusttype::Font<'static>,
|
||||
glyph: rusttype::Glyph<'static>,
|
||||
scale_in_pixels: f32,
|
||||
pixels_per_point: f32,
|
||||
) -> Option<GlyphInfo> {
|
||||
let glyph = font.glyph(c);
|
||||
if glyph.id().0 == 0 {
|
||||
return None; // Failed to find a glyph for the character
|
||||
}
|
||||
) -> GlyphInfo {
|
||||
assert!(glyph.id().0 != 0);
|
||||
|
||||
let glyph = glyph.scaled(Scale::uniform(scale_in_pixels));
|
||||
let glyph = glyph.positioned(point(0.0, 0.0));
|
||||
|
@ -382,33 +457,36 @@ fn allocate_glyph(
|
|||
let uv_rect = if let Some(bb) = glyph.pixel_bounding_box() {
|
||||
let glyph_width = bb.width() as usize;
|
||||
let glyph_height = bb.height() as usize;
|
||||
assert!(glyph_width >= 1);
|
||||
assert!(glyph_height >= 1);
|
||||
|
||||
let glyph_pos = atlas.allocate((glyph_width, glyph_height));
|
||||
if glyph_width == 0 || glyph_height == 0 {
|
||||
None
|
||||
} else {
|
||||
let glyph_pos = atlas.allocate((glyph_width, glyph_height));
|
||||
|
||||
let texture = atlas.texture_mut();
|
||||
glyph.draw(|x, y, v| {
|
||||
if v > 0.0 {
|
||||
let px = glyph_pos.0 + x as usize;
|
||||
let py = glyph_pos.1 + y as usize;
|
||||
texture[(px, py)] = (v * 255.0).round() as u8;
|
||||
}
|
||||
});
|
||||
let texture = atlas.texture_mut();
|
||||
glyph.draw(|x, y, v| {
|
||||
if v > 0.0 {
|
||||
let px = glyph_pos.0 + x as usize;
|
||||
let py = glyph_pos.1 + y as usize;
|
||||
texture[(px, py)] = (v * 255.0).round() as u8;
|
||||
}
|
||||
});
|
||||
|
||||
let offset_y_in_pixels = scale_in_pixels as f32 + bb.min.y as f32 - 4.0 * pixels_per_point; // TODO: use font.v_metrics
|
||||
Some(UvRect {
|
||||
offset: vec2(
|
||||
bb.min.x as f32 / pixels_per_point,
|
||||
offset_y_in_pixels / pixels_per_point,
|
||||
),
|
||||
size: vec2(glyph_width as f32, glyph_height as f32) / pixels_per_point,
|
||||
min: (glyph_pos.0 as u16, glyph_pos.1 as u16),
|
||||
max: (
|
||||
(glyph_pos.0 + glyph_width) as u16,
|
||||
(glyph_pos.1 + glyph_height) as u16,
|
||||
),
|
||||
})
|
||||
let offset_y_in_pixels =
|
||||
scale_in_pixels as f32 + bb.min.y as f32 - 4.0 * pixels_per_point; // TODO: use font.v_metrics
|
||||
Some(UvRect {
|
||||
offset: vec2(
|
||||
bb.min.x as f32 / pixels_per_point,
|
||||
offset_y_in_pixels / pixels_per_point,
|
||||
),
|
||||
size: vec2(glyph_width as f32, glyph_height as f32) / pixels_per_point,
|
||||
min: (glyph_pos.0 as u16, glyph_pos.1 as u16),
|
||||
max: (
|
||||
(glyph_pos.0 + glyph_width) as u16,
|
||||
(glyph_pos.1 + glyph_height) as u16,
|
||||
),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// No bounding box. Maybe a space?
|
||||
None
|
||||
|
@ -416,9 +494,9 @@ fn allocate_glyph(
|
|||
|
||||
let advance_width_in_points = glyph.unpositioned().h_metrics().advance_width / pixels_per_point;
|
||||
|
||||
Some(GlyphInfo {
|
||||
GlyphInfo {
|
||||
id: glyph.id(),
|
||||
advance_width: advance_width_in_points,
|
||||
uv_rect,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
|||
use crate::mutex::Mutex;
|
||||
|
||||
use super::{
|
||||
font::Font,
|
||||
font::{Font, FontImpl},
|
||||
texture_atlas::{Texture, TextureAtlas},
|
||||
};
|
||||
|
||||
|
@ -23,6 +23,20 @@ pub enum TextStyle {
|
|||
Monospace,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn all() -> impl Iterator<Item = TextStyle> {
|
||||
[
|
||||
TextStyle::Small,
|
||||
TextStyle::Body,
|
||||
TextStyle::Button,
|
||||
TextStyle::Heading,
|
||||
TextStyle::Monospace,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
// #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum FontFamily {
|
||||
|
@ -41,6 +55,9 @@ pub struct FontDefinitions {
|
|||
/// Egui has built-in-default for these,
|
||||
/// but you can override them if you like.
|
||||
pub ttf_data: BTreeMap<FontFamily, &'static [u8]>,
|
||||
|
||||
/// ttf data for emoji font(s), if any, in order of preference
|
||||
pub emoji_ttf_data: Vec<&'static [u8]>,
|
||||
}
|
||||
|
||||
impl Default for FontDefinitions {
|
||||
|
@ -70,6 +87,10 @@ impl FontDefinitions {
|
|||
pixels_per_point,
|
||||
fonts,
|
||||
ttf_data,
|
||||
emoji_ttf_data: vec![
|
||||
include_bytes!("../../fonts/NotoEmoji-Regular.ttf"), // few, but good looking. Use as first priority
|
||||
include_bytes!("../../fonts/emoji-icon-font.ttf"), // bigger and more: http://jslegers.github.io/emoji-icon-font/
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +122,9 @@ impl Fonts {
|
|||
return;
|
||||
}
|
||||
|
||||
let mut atlas = TextureAtlas::new(512, 16); // TODO: better default?
|
||||
// We want an atlas big enough to be able to include all the Emojis in the `TextStyle::Heading`,
|
||||
// so we can show the Emoji picker demo window.
|
||||
let mut atlas = TextureAtlas::new(2048, 64);
|
||||
|
||||
{
|
||||
// Make the top left pixel fully white:
|
||||
|
@ -112,21 +135,33 @@ impl Fonts {
|
|||
|
||||
let atlas = Arc::new(Mutex::new(atlas));
|
||||
|
||||
self.definitions = definitions.clone();
|
||||
let FontDefinitions {
|
||||
pixels_per_point,
|
||||
fonts,
|
||||
ttf_data,
|
||||
} = definitions;
|
||||
self.fonts = fonts
|
||||
.into_iter()
|
||||
.map(|(text_style, (family, size))| {
|
||||
let typeface_data = ttf_data
|
||||
.get(&family)
|
||||
.unwrap_or_else(|| panic!("Missing TTF data for {:?}", family));
|
||||
let font = Font::new(atlas.clone(), typeface_data, size, pixels_per_point);
|
||||
self.definitions = definitions;
|
||||
|
||||
(text_style, font)
|
||||
let mut font_impl_cache = FontImplCache::new(atlas.clone(), &self.definitions);
|
||||
|
||||
self.fonts = self
|
||||
.definitions
|
||||
.fonts
|
||||
.iter()
|
||||
.map(|(&text_style, &(family, size))| {
|
||||
let mut fonts = vec![];
|
||||
|
||||
fonts.push(font_impl_cache.font_impl(FontSource::Family(family), size));
|
||||
|
||||
if family == FontFamily::Monospace {
|
||||
// monospace should have ubuntu as fallback (for √ etc):
|
||||
fonts.push(
|
||||
font_impl_cache
|
||||
.font_impl(FontSource::Family(FontFamily::VariableWidth), size),
|
||||
);
|
||||
}
|
||||
|
||||
for index in 0..self.definitions.emoji_ttf_data.len() {
|
||||
let emoji_font_impl = font_impl_cache.font_impl(FontSource::Emoji(index), size);
|
||||
fonts.push(emoji_font_impl);
|
||||
}
|
||||
|
||||
(text_style, Font::new(fonts))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -162,3 +197,79 @@ impl std::ops::Index<TextStyle> for Fonts {
|
|||
&self.fonts[&text_style]
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum FontSource {
|
||||
Family(FontFamily),
|
||||
/// Emoji fonts are numbered from hight priority (0) and onwards
|
||||
Emoji(usize),
|
||||
}
|
||||
|
||||
pub struct FontImplCache {
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
pixels_per_point: f32,
|
||||
font_families: std::collections::BTreeMap<FontFamily, Arc<rusttype::Font<'static>>>,
|
||||
emoji_fonts: Vec<Arc<rusttype::Font<'static>>>,
|
||||
|
||||
/// can't have f32 in a HashMap or BTreeMap,
|
||||
/// so let's do a linear search
|
||||
cache: Vec<(FontSource, f32, Arc<FontImpl>)>,
|
||||
}
|
||||
|
||||
impl FontImplCache {
|
||||
pub fn new(atlas: Arc<Mutex<TextureAtlas>>, definitions: &super::FontDefinitions) -> Self {
|
||||
let font_families = definitions
|
||||
.ttf_data
|
||||
.iter()
|
||||
.map(|(family, ttf_data)| {
|
||||
(
|
||||
*family,
|
||||
Arc::new(rusttype::Font::try_from_bytes(ttf_data).expect("Error parsing TTF")),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let emoji_fonts = definitions
|
||||
.emoji_ttf_data
|
||||
.iter()
|
||||
.map(|ttf_data| {
|
||||
Arc::new(rusttype::Font::try_from_bytes(ttf_data).expect("Error parsing TTF"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
atlas,
|
||||
pixels_per_point: definitions.pixels_per_point,
|
||||
font_families,
|
||||
emoji_fonts,
|
||||
cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rusttype_font(&self, source: FontSource) -> Arc<rusttype::Font<'static>> {
|
||||
match source {
|
||||
FontSource::Family(family) => self.font_families.get(&family).unwrap().clone(),
|
||||
FontSource::Emoji(index) => self.emoji_fonts[index].clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn font_impl(&mut self, source: FontSource, scale_in_points: f32) -> Arc<FontImpl> {
|
||||
for entry in &self.cache {
|
||||
if (entry.0, entry.1) == (source, scale_in_points) {
|
||||
return entry.2.clone();
|
||||
}
|
||||
}
|
||||
|
||||
let font_impl = Arc::new(FontImpl::new(
|
||||
self.atlas.clone(),
|
||||
self.pixels_per_point,
|
||||
self.rusttype_font(source),
|
||||
scale_in_points,
|
||||
));
|
||||
self.cache
|
||||
.push((source, scale_in_points, font_impl.clone()));
|
||||
font_impl
|
||||
}
|
||||
}
|
||||
|
|
|
@ -654,14 +654,11 @@ fn test_text_layout() {
|
|||
}
|
||||
}
|
||||
|
||||
use crate::mutex::Mutex;
|
||||
use crate::paint::{font::Font, *};
|
||||
use crate::paint::*;
|
||||
|
||||
let pixels_per_point = 1.0;
|
||||
let typeface_data = include_bytes!("../../fonts/ProggyClean.ttf");
|
||||
let atlas = TextureAtlas::new(512, 16);
|
||||
let atlas = std::sync::Arc::new(Mutex::new(atlas));
|
||||
let font = Font::new(atlas, typeface_data, 13.0, pixels_per_point);
|
||||
let fonts = Fonts::from_definitions(FontDefinitions::with_pixels_per_point(pixels_per_point));
|
||||
let font = &fonts[TextStyle::Monospace];
|
||||
|
||||
let galley = font.layout_multiline("".to_owned(), 1024.0);
|
||||
assert_eq!(galley.rows.len(), 1);
|
||||
|
|
|
@ -139,11 +139,10 @@ impl Painter {
|
|||
self.text(rect.min, LEFT_TOP, text.into(), text_style, color);
|
||||
}
|
||||
|
||||
pub fn error(&self, pos: Pos2, text: impl Into<String>) {
|
||||
let text = text.into();
|
||||
pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) {
|
||||
let text_style = TextStyle::Monospace;
|
||||
let font = &self.fonts()[text_style];
|
||||
let galley = font.layout_multiline(text, f32::INFINITY);
|
||||
let galley = font.layout_multiline(format!("🔥 {}", text), f32::INFINITY);
|
||||
let rect = anchor_rect(Rect::from_min_size(pos, galley.size), LEFT_TOP);
|
||||
self.add(PaintCmd::Rect {
|
||||
rect: rect.expand(2.0),
|
||||
|
|
|
@ -355,9 +355,9 @@ impl Style {
|
|||
ui.radio_value(body_text_style, value, format!("{:?}", value));
|
||||
}
|
||||
});
|
||||
ui.collapsing("Spacing", |ui| spacing.ui(ui));
|
||||
ui.collapsing("Interaction", |ui| interaction.ui(ui));
|
||||
ui.collapsing("Visuals", |ui| visuals.ui(ui));
|
||||
ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
|
||||
ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
|
||||
ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
|
||||
ui.add(Slider::f32(animation_time, 0.0..=1.0).text("animation_time"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -288,6 +288,7 @@ pub struct Button {
|
|||
fill: Option<Srgba>,
|
||||
sense: Sense,
|
||||
small: bool,
|
||||
frame: bool,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
|
@ -299,6 +300,7 @@ impl Button {
|
|||
fill: Default::default(),
|
||||
sense: Sense::click(),
|
||||
small: false,
|
||||
frame: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,6 +331,12 @@ impl Button {
|
|||
self
|
||||
}
|
||||
|
||||
/// Turn off the frame
|
||||
pub fn frame(mut self, frame: bool) -> Self {
|
||||
self.frame = frame;
|
||||
self
|
||||
}
|
||||
|
||||
/// By default, buttons senses clicks.
|
||||
/// Change this to a drag-button with `Sense::drag()`.
|
||||
pub fn sense(mut self, sense: Sense) -> Self {
|
||||
|
@ -355,6 +363,7 @@ impl Widget for Button {
|
|||
fill,
|
||||
sense,
|
||||
small,
|
||||
frame,
|
||||
} = self;
|
||||
|
||||
let mut button_padding = ui.style().spacing.button_padding;
|
||||
|
@ -363,32 +372,47 @@ impl Widget for Button {
|
|||
}
|
||||
|
||||
let font = &ui.fonts()[text_style];
|
||||
let galley = font.layout_multiline(text, ui.available_width());
|
||||
|
||||
let single_line = ui.layout().is_horizontal();
|
||||
let galley = if single_line {
|
||||
font.layout_single_line(text)
|
||||
} else {
|
||||
font.layout_multiline(text, ui.available_width())
|
||||
};
|
||||
|
||||
let mut desired_size = galley.size + 2.0 * button_padding;
|
||||
if !small {
|
||||
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
|
||||
desired_size.y = desired_size.y.at_least(ui.style().spacing.interact_size.y);
|
||||
}
|
||||
let rect = ui.allocate_space(desired_size);
|
||||
|
||||
let id = ui.make_position_id();
|
||||
let response = ui.interact(rect, id, sense);
|
||||
let visuals = ui.style().interact(&response);
|
||||
let text_cursor = ui
|
||||
.layout()
|
||||
.align_size_within_rect(galley.size, response.rect.shrink2(button_padding))
|
||||
.min;
|
||||
let fill = fill.unwrap_or(visuals.bg_fill);
|
||||
ui.painter().rect(
|
||||
response.rect,
|
||||
visuals.corner_radius,
|
||||
fill,
|
||||
visuals.bg_stroke,
|
||||
);
|
||||
let text_color = text_color
|
||||
.or(ui.style().visuals.override_text_color)
|
||||
.unwrap_or_else(|| visuals.text_color());
|
||||
ui.painter()
|
||||
.galley(text_cursor, galley, text_style, text_color);
|
||||
|
||||
if ui.clip_rect().intersects(rect) {
|
||||
let visuals = ui.style().interact(&response);
|
||||
let text_cursor = ui
|
||||
.layout()
|
||||
.align_size_within_rect(galley.size, response.rect.shrink2(button_padding))
|
||||
.min;
|
||||
|
||||
if frame {
|
||||
let fill = fill.unwrap_or(visuals.bg_fill);
|
||||
ui.painter().rect(
|
||||
response.rect,
|
||||
visuals.corner_radius,
|
||||
fill,
|
||||
visuals.bg_stroke,
|
||||
);
|
||||
}
|
||||
|
||||
let text_color = text_color
|
||||
.or(ui.style().visuals.override_text_color)
|
||||
.unwrap_or_else(|| visuals.text_color());
|
||||
ui.painter()
|
||||
.galley(text_cursor, galley, text_style, text_color);
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
@ -436,8 +460,12 @@ impl<'a> Widget for Checkbox<'a> {
|
|||
let button_padding = spacing.button_padding;
|
||||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||
|
||||
let galley = font.layout_single_line(text);
|
||||
// let galley = font.layout_multiline(text, ui.available_width() - total_extra.x);
|
||||
let single_line = ui.layout().is_horizontal();
|
||||
let galley = if single_line {
|
||||
font.layout_single_line(text)
|
||||
} else {
|
||||
font.layout_multiline(text, ui.available_width() - total_extra.x)
|
||||
};
|
||||
|
||||
let mut desired_size = total_extra + galley.size;
|
||||
desired_size = desired_size.at_least(spacing.interact_size);
|
||||
|
@ -526,7 +554,12 @@ impl Widget for RadioButton {
|
|||
let button_padding = ui.style().spacing.button_padding;
|
||||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||
|
||||
let galley = font.layout_multiline(text, ui.available_width() - total_extra.x);
|
||||
let single_line = ui.layout().is_horizontal();
|
||||
let galley = if single_line {
|
||||
font.layout_single_line(text)
|
||||
} else {
|
||||
font.layout_multiline(text, ui.available_width() - total_extra.x)
|
||||
};
|
||||
|
||||
let mut desired_size = total_extra + galley.size;
|
||||
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
|
||||
|
|
Loading…
Reference in a new issue