mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-28 16:00:58 +00:00
New Widgets: Manually port important changes from 'feature/color-palette' branch
This commit is contained in:
parent
45180003de
commit
12075d4243
3
assets/magnifyingglass.svg
Executable file
3
assets/magnifyingglass.svg
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="#FFFFFF" d="m4.50586 9.44238c.97852 0 1.88086-.3164 2.61914-.84375l2.77734 2.77737c.12886.1289.29886.1933.48046.1933.3809 0 .6504-.2929.6504-.6679 0-.1758-.0586-.3399-.1875-.4688l-2.75976-2.76561c.58008-.76172.92578-1.70508.92578-2.73047 0-2.47851-2.02734-4.505856-4.50586-4.505856-2.47266 0-4.50586 2.021486-4.50586 4.505856 0 2.47852 2.02734 4.50586 4.50586 4.50586zm0-.97265c-1.93359 0-3.533204-1.59961-3.533204-3.53321 0-1.93359 1.599614-3.5332 3.533204-3.5332s3.5332 1.59961 3.5332 3.5332c0 1.9336-1.59961 3.53321-3.5332 3.53321z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 643 B |
68
gossip-bin/src/ui/assets.rs
Normal file
68
gossip-bin/src/ui/assets.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
use egui_winit::egui::{ColorImage, Context, TextureHandle, TextureOptions};
|
||||||
|
use tiny_skia::Transform;
|
||||||
|
use usvg::TreeParsing;
|
||||||
|
|
||||||
|
pub const SVG_OVERSAMPLE: f32 = 2.0;
|
||||||
|
|
||||||
|
pub struct Assets {
|
||||||
|
pub options_symbol: TextureHandle,
|
||||||
|
pub magnifyingglass_symbol: TextureHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Assets {
|
||||||
|
pub fn init(ctx: &Context) -> Self {
|
||||||
|
// how to load an svg
|
||||||
|
let ppt = ctx.pixels_per_point();
|
||||||
|
let dpi = ppt * 72.0;
|
||||||
|
let options_symbol = {
|
||||||
|
let bytes = include_bytes!("../../../assets/option.svg");
|
||||||
|
let opt = usvg::Options {
|
||||||
|
dpi,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let rtree = usvg::Tree::from_data(bytes, &opt).unwrap();
|
||||||
|
let [w, h] = [
|
||||||
|
(rtree.size.width() * ppt * SVG_OVERSAMPLE) as u32,
|
||||||
|
(rtree.size.height() * ppt * SVG_OVERSAMPLE) as u32,
|
||||||
|
];
|
||||||
|
let mut pixmap = tiny_skia::Pixmap::new(w, h).unwrap();
|
||||||
|
let tree = resvg::Tree::from_usvg(&rtree);
|
||||||
|
tree.render(
|
||||||
|
Transform::from_scale(ppt * SVG_OVERSAMPLE, ppt * SVG_OVERSAMPLE),
|
||||||
|
&mut pixmap.as_mut(),
|
||||||
|
);
|
||||||
|
let color_image = ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data());
|
||||||
|
ctx.load_texture("options_symbol", color_image, TextureOptions::LINEAR)
|
||||||
|
};
|
||||||
|
|
||||||
|
let magnifyingglass_symbol = {
|
||||||
|
let bytes = include_bytes!("../../../assets/magnifyingglass.svg");
|
||||||
|
let opt = usvg::Options {
|
||||||
|
dpi,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let rtree = usvg::Tree::from_data(bytes, &opt).unwrap();
|
||||||
|
let [w, h] = [
|
||||||
|
(rtree.size.width() * ppt * SVG_OVERSAMPLE) as u32,
|
||||||
|
(rtree.size.height() * ppt * SVG_OVERSAMPLE) as u32,
|
||||||
|
];
|
||||||
|
let mut pixmap = tiny_skia::Pixmap::new(w, h).unwrap();
|
||||||
|
let tree = resvg::Tree::from_usvg(&rtree);
|
||||||
|
tree.render(
|
||||||
|
Transform::from_scale(ppt * SVG_OVERSAMPLE, ppt * SVG_OVERSAMPLE),
|
||||||
|
&mut pixmap.as_mut(),
|
||||||
|
);
|
||||||
|
let color_image = ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data());
|
||||||
|
ctx.load_texture(
|
||||||
|
"magnifyingglass_symbol",
|
||||||
|
color_image,
|
||||||
|
TextureOptions::LINEAR,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
magnifyingglass_symbol,
|
||||||
|
options_symbol,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -104,8 +104,8 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, ui: &mut Ui) {
|
|||||||
|
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
ui.label(RichText::new("Include replies").size(11.0));
|
ui.label(RichText::new("Include replies").size(11.0));
|
||||||
let size = ui.spacing().interact_size.y * egui::vec2(1.6, 0.8);
|
if widgets::Switch::small(&app.theme, &mut app.mainfeed_include_nonroot)
|
||||||
if widgets::switch_with_size(ui, &mut app.mainfeed_include_nonroot, size)
|
.show(ui)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
app.set_page(
|
app.set_page(
|
||||||
@ -150,8 +150,8 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, ui: &mut Ui) {
|
|||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
ui.add_space(16.0);
|
ui.add_space(16.0);
|
||||||
ui.label(RichText::new("Everything").size(11.0));
|
ui.label(RichText::new("Everything").size(11.0));
|
||||||
let size = ui.spacing().interact_size.y * egui::vec2(1.6, 0.8);
|
if widgets::Switch::small(&app.theme, &mut app.inbox_include_indirect)
|
||||||
if widgets::switch_with_size(ui, &mut app.inbox_include_indirect, size)
|
.show(ui)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
app.set_page(
|
app.set_page(
|
||||||
|
@ -5,7 +5,6 @@ use gossip_lib::PersonList;
|
|||||||
|
|
||||||
mod about;
|
mod about;
|
||||||
mod stats;
|
mod stats;
|
||||||
mod theme;
|
|
||||||
|
|
||||||
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||||
if app.page == Page::HelpHelp {
|
if app.page == Page::HelpHelp {
|
||||||
@ -81,7 +80,5 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
stats::update(app, ctx, _frame, ui);
|
stats::update(app, ctx, _frame, ui);
|
||||||
} else if app.page == Page::HelpAbout {
|
} else if app.page == Page::HelpAbout {
|
||||||
about::update(app, ctx, _frame, ui);
|
about::update(app, ctx, _frame, ui);
|
||||||
} else if app.page == Page::HelpTheme {
|
|
||||||
theme::update(app, ctx, _frame, ui);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,145 +0,0 @@
|
|||||||
use super::GossipUi;
|
|
||||||
use crate::ui::feed::NoteRenderData;
|
|
||||||
use crate::ui::HighlightType;
|
|
||||||
use eframe::egui;
|
|
||||||
use egui::text::LayoutJob;
|
|
||||||
use egui::widget_text::WidgetText;
|
|
||||||
use egui::{Color32, Context, Frame, Margin, RichText, Ui};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum Background {
|
|
||||||
None,
|
|
||||||
Input,
|
|
||||||
Note,
|
|
||||||
HighlightedNote,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
|
||||||
ui.add_space(10.0);
|
|
||||||
ui.heading("Theme Test".to_string());
|
|
||||||
ui.add_space(12.0);
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
// On No Background
|
|
||||||
Frame::none()
|
|
||||||
.inner_margin(Margin::symmetric(20.0, 20.0))
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.heading("No Background");
|
|
||||||
inner(app, ui, Background::None);
|
|
||||||
});
|
|
||||||
|
|
||||||
// On Note Background
|
|
||||||
let render_data = NoteRenderData {
|
|
||||||
height: 200.0,
|
|
||||||
is_new: false,
|
|
||||||
is_main_event: false,
|
|
||||||
has_repost: false,
|
|
||||||
is_comment_mention: false,
|
|
||||||
is_thread: false,
|
|
||||||
is_first: true,
|
|
||||||
is_last: true,
|
|
||||||
thread_position: 0,
|
|
||||||
};
|
|
||||||
Frame::none()
|
|
||||||
.inner_margin(app.theme.feed_frame_inner_margin(&render_data))
|
|
||||||
.outer_margin(app.theme.feed_frame_outer_margin(&render_data))
|
|
||||||
.rounding(app.theme.feed_frame_rounding(&render_data))
|
|
||||||
.shadow(app.theme.feed_frame_shadow(&render_data))
|
|
||||||
.fill(app.theme.feed_frame_fill(&render_data))
|
|
||||||
.stroke(app.theme.feed_frame_stroke(&render_data))
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.heading("Note Background");
|
|
||||||
ui.label("(with note margins)");
|
|
||||||
inner(app, ui, Background::Note);
|
|
||||||
});
|
|
||||||
|
|
||||||
// On Highlighted Note Background
|
|
||||||
let render_data = NoteRenderData {
|
|
||||||
height: 200.0,
|
|
||||||
is_new: true,
|
|
||||||
is_main_event: false,
|
|
||||||
has_repost: false,
|
|
||||||
is_comment_mention: false,
|
|
||||||
is_thread: false,
|
|
||||||
is_first: true,
|
|
||||||
is_last: true,
|
|
||||||
thread_position: 0,
|
|
||||||
};
|
|
||||||
Frame::none()
|
|
||||||
.inner_margin(app.theme.feed_frame_inner_margin(&render_data))
|
|
||||||
.outer_margin(app.theme.feed_frame_outer_margin(&render_data))
|
|
||||||
.rounding(app.theme.feed_frame_rounding(&render_data))
|
|
||||||
.shadow(app.theme.feed_frame_shadow(&render_data))
|
|
||||||
.fill(app.theme.feed_frame_fill(&render_data))
|
|
||||||
.stroke(app.theme.feed_frame_stroke(&render_data))
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.heading("Unread Note Background");
|
|
||||||
ui.label("(with note margins)");
|
|
||||||
inner(app, ui, Background::HighlightedNote);
|
|
||||||
});
|
|
||||||
|
|
||||||
// On Input Background
|
|
||||||
Frame::none()
|
|
||||||
.fill(app.theme.get_style().visuals.extreme_bg_color)
|
|
||||||
.inner_margin(Margin::symmetric(20.0, 20.0))
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.heading("Input Background");
|
|
||||||
inner(app, ui, Background::Input);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inner(app: &mut GossipUi, ui: &mut Ui, background: Background) {
|
|
||||||
let theme = app.theme;
|
|
||||||
let accent = RichText::new("accent").color(theme.accent_color());
|
|
||||||
let accent_complementary = RichText::new("accent complimentary (indirectly used)")
|
|
||||||
.color(theme.accent_complementary_color());
|
|
||||||
|
|
||||||
line(ui, accent);
|
|
||||||
line(ui, accent_complementary);
|
|
||||||
|
|
||||||
if background == Background::Input {
|
|
||||||
for (ht, txt) in [
|
|
||||||
(HighlightType::Nothing, "nothing"),
|
|
||||||
(HighlightType::PublicKey, "public key"),
|
|
||||||
(HighlightType::Event, "event"),
|
|
||||||
(HighlightType::Relay, "relay"),
|
|
||||||
(HighlightType::Hyperlink, "hyperlink"),
|
|
||||||
] {
|
|
||||||
let mut highlight_job = LayoutJob::default();
|
|
||||||
highlight_job.append(
|
|
||||||
&format!("highlight text format for {}", txt),
|
|
||||||
0.0,
|
|
||||||
theme.highlight_text_format(ht),
|
|
||||||
);
|
|
||||||
line(ui, WidgetText::LayoutJob(highlight_job));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if background == Background::Note || background == Background::HighlightedNote {
|
|
||||||
let warning_marker =
|
|
||||||
RichText::new("warning marker").color(theme.warning_marker_text_color());
|
|
||||||
line(ui, warning_marker);
|
|
||||||
|
|
||||||
let notice_marker = RichText::new("notice marker").color(theme.notice_marker_text_color());
|
|
||||||
line(ui, notice_marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
if background != Background::Input {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(RichText::new("•").color(Color32::from_gray(128)));
|
|
||||||
crate::ui::widgets::break_anywhere_hyperlink_to(
|
|
||||||
ui,
|
|
||||||
"https://hyperlink.example.com",
|
|
||||||
"https://hyperlink.example.com",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line(ui: &mut Ui, label: impl Into<WidgetText>) {
|
|
||||||
let bullet = RichText::new("•").color(Color32::from_gray(128));
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(bullet);
|
|
||||||
ui.label(label);
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
macro_rules! text_edit_line {
|
macro_rules! text_edit_line {
|
||||||
($app:ident, $var:expr) => {
|
($app:ident, $var:expr) => {
|
||||||
crate::ui::widgets::TextEdit::singleline(&mut $var)
|
crate::ui::widgets::TextEdit::singleline(&$app.theme, &mut $var)
|
||||||
.text_color($app.theme.input_text_color())
|
.text_color($app.theme.input_text_color())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -33,6 +33,7 @@ macro_rules! write_setting {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod assets;
|
||||||
mod components;
|
mod components;
|
||||||
mod dm_chat_list;
|
mod dm_chat_list;
|
||||||
mod feed;
|
mod feed;
|
||||||
@ -76,9 +77,9 @@ use std::hash::Hash;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use usvg::TreeParsing;
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use self::assets::Assets;
|
||||||
use self::feed::Notes;
|
use self::feed::Notes;
|
||||||
use self::notifications::NotificationData;
|
use self::notifications::NotificationData;
|
||||||
use self::widgets::NavItem;
|
use self::widgets::NavItem;
|
||||||
@ -157,7 +158,8 @@ enum Page {
|
|||||||
HelpHelp,
|
HelpHelp,
|
||||||
HelpStats,
|
HelpStats,
|
||||||
HelpAbout,
|
HelpAbout,
|
||||||
HelpTheme,
|
#[allow(unused)]
|
||||||
|
ThemeTest,
|
||||||
Wizard(WizardPage),
|
Wizard(WizardPage),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +206,7 @@ impl Page {
|
|||||||
Page::HelpHelp => (SubMenu::Help.as_str(), "Troubleshooting".into()),
|
Page::HelpHelp => (SubMenu::Help.as_str(), "Troubleshooting".into()),
|
||||||
Page::HelpStats => (SubMenu::Help.as_str(), "Stats".into()),
|
Page::HelpStats => (SubMenu::Help.as_str(), "Stats".into()),
|
||||||
Page::HelpAbout => (SubMenu::Help.as_str(), "About".into()),
|
Page::HelpAbout => (SubMenu::Help.as_str(), "About".into()),
|
||||||
Page::HelpTheme => (SubMenu::Help.as_str(), "Theme Test".into()),
|
Page::ThemeTest => (SubMenu::Help.as_str(), "Theme Test".into()),
|
||||||
Page::Wizard(wp) => ("Wizard", wp.as_str().to_string()),
|
Page::Wizard(wp) => ("Wizard", wp.as_str().to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,10 +435,10 @@ struct GossipUi {
|
|||||||
feeds: feed::Feeds,
|
feeds: feed::Feeds,
|
||||||
|
|
||||||
// General Data
|
// General Data
|
||||||
|
assets: Assets,
|
||||||
about: About,
|
about: About,
|
||||||
icon: TextureHandle,
|
icon: TextureHandle,
|
||||||
placeholder_avatar: TextureHandle,
|
placeholder_avatar: TextureHandle,
|
||||||
options_symbol: TextureHandle,
|
|
||||||
unsaved_settings: UnsavedSettings,
|
unsaved_settings: UnsavedSettings,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
avatars: HashMap<PublicKey, TextureHandle>,
|
avatars: HashMap<PublicKey, TextureHandle>,
|
||||||
@ -508,6 +510,8 @@ struct GossipUi {
|
|||||||
|
|
||||||
wizard_state: WizardState,
|
wizard_state: WizardState,
|
||||||
|
|
||||||
|
theme_test: crate::ui::theme::test_page::ThemeTest,
|
||||||
|
|
||||||
// Cached DM Channels
|
// Cached DM Channels
|
||||||
dm_channel_cache: Vec<DmChannelData>,
|
dm_channel_cache: Vec<DmChannelData>,
|
||||||
dm_channel_next_refresh: Instant,
|
dm_channel_next_refresh: Instant,
|
||||||
@ -544,6 +548,9 @@ impl GossipUi {
|
|||||||
submenu_ids.insert(SubMenu::Relays, egui::Id::new(SubMenu::Relays.as_id_str()));
|
submenu_ids.insert(SubMenu::Relays, egui::Id::new(SubMenu::Relays.as_id_str()));
|
||||||
submenu_ids.insert(SubMenu::Help, egui::Id::new(SubMenu::Help.as_id_str()));
|
submenu_ids.insert(SubMenu::Help, egui::Id::new(SubMenu::Help.as_id_str()));
|
||||||
|
|
||||||
|
// load Assets, but load again when DPI changes
|
||||||
|
let assets = Assets::init(&cctx.egui_ctx);
|
||||||
|
|
||||||
let icon_texture_handle = {
|
let icon_texture_handle = {
|
||||||
let bytes = include_bytes!("../../../logo/gossip.png");
|
let bytes = include_bytes!("../../../logo/gossip.png");
|
||||||
let image = image::load_from_memory(bytes).unwrap();
|
let image = image::load_from_memory(bytes).unwrap();
|
||||||
@ -594,23 +601,6 @@ impl GossipUi {
|
|||||||
None => (false, (cctx.egui_ctx.pixels_per_point() * 72.0) as u32),
|
None => (false, (cctx.egui_ctx.pixels_per_point() * 72.0) as u32),
|
||||||
};
|
};
|
||||||
|
|
||||||
// how to load an svg (TODO do again when DPI changes)
|
|
||||||
let options_symbol = {
|
|
||||||
let bytes = include_bytes!("../../../assets/option.svg");
|
|
||||||
let opt = usvg::Options {
|
|
||||||
dpi: override_dpi_value as f32,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let rtree = usvg::Tree::from_data(bytes, &opt).unwrap();
|
|
||||||
let [w, h] = [20_u32, 20_u32];
|
|
||||||
let mut pixmap = tiny_skia::Pixmap::new(w, h).unwrap();
|
|
||||||
let tree = resvg::Tree::from_usvg(&rtree);
|
|
||||||
tree.render(Default::default(), &mut pixmap.as_mut());
|
|
||||||
let color_image = ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data());
|
|
||||||
cctx.egui_ctx
|
|
||||||
.load_texture("options_symbol", color_image, TextureOptions::LINEAR)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mainfeed_include_nonroot = cctx
|
let mainfeed_include_nonroot = cctx
|
||||||
.egui_ctx
|
.egui_ctx
|
||||||
.data_mut(|d| d.get_persisted(egui::Id::new("mainfeed_include_nonroot")))
|
.data_mut(|d| d.get_persisted(egui::Id::new("mainfeed_include_nonroot")))
|
||||||
@ -681,10 +671,11 @@ impl GossipUi {
|
|||||||
submenu_ids,
|
submenu_ids,
|
||||||
settings_tab: SettingsTab::Id,
|
settings_tab: SettingsTab::Id,
|
||||||
feeds: feed::Feeds::default(),
|
feeds: feed::Feeds::default(),
|
||||||
|
// load Assets, but load again when DPI changes
|
||||||
|
assets,
|
||||||
about: About::new(),
|
about: About::new(),
|
||||||
icon: icon_texture_handle,
|
icon: icon_texture_handle,
|
||||||
placeholder_avatar: placeholder_avatar_texture_handle,
|
placeholder_avatar: placeholder_avatar_texture_handle,
|
||||||
options_symbol,
|
|
||||||
unsaved_settings: UnsavedSettings::load(),
|
unsaved_settings: UnsavedSettings::load(),
|
||||||
theme,
|
theme,
|
||||||
avatars: HashMap::new(),
|
avatars: HashMap::new(),
|
||||||
@ -731,6 +722,7 @@ impl GossipUi {
|
|||||||
zap_state: ZapState::None,
|
zap_state: ZapState::None,
|
||||||
note_being_zapped: None,
|
note_being_zapped: None,
|
||||||
wizard_state,
|
wizard_state,
|
||||||
|
theme_test: Default::default(),
|
||||||
dm_channel_cache: vec![],
|
dm_channel_cache: vec![],
|
||||||
dm_channel_next_refresh: Instant::now(),
|
dm_channel_next_refresh: Instant::now(),
|
||||||
dm_channel_error: None,
|
dm_channel_error: None,
|
||||||
@ -762,21 +754,8 @@ impl GossipUi {
|
|||||||
// 'original' refers to 'before the user changes it in settings'
|
// 'original' refers to 'before the user changes it in settings'
|
||||||
self.original_dpi_value = self.override_dpi_value;
|
self.original_dpi_value = self.override_dpi_value;
|
||||||
|
|
||||||
// load SVG's again when DPI changes
|
// Reload Assets when DPI changes
|
||||||
self.options_symbol = {
|
self.assets = Assets::init(ctx);
|
||||||
let bytes = include_bytes!("../../../assets/option.svg");
|
|
||||||
let opt = usvg::Options {
|
|
||||||
dpi: self.override_dpi_value as f32,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let rtree = usvg::Tree::from_data(bytes, &opt).unwrap();
|
|
||||||
let [w, h] = [20_u32, 20_u32];
|
|
||||||
let mut pixmap = tiny_skia::Pixmap::new(w, h).unwrap();
|
|
||||||
let tree = resvg::Tree::from_usvg(&rtree);
|
|
||||||
tree.render(Default::default(), &mut pixmap.as_mut());
|
|
||||||
let color_image = ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data());
|
|
||||||
ctx.load_texture("options_symbol", color_image, TextureOptions::LINEAR)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set global pixels_per_point_times_100, used for image scaling.
|
// Set global pixels_per_point_times_100, used for image scaling.
|
||||||
// this would warrant reloading images but the user experience isn't great as
|
// this would warrant reloading images but the user experience isn't great as
|
||||||
@ -900,7 +879,7 @@ impl GossipUi {
|
|||||||
Page::Settings => {
|
Page::Settings => {
|
||||||
self.close_all_menus_except_feeds(ctx);
|
self.close_all_menus_except_feeds(ctx);
|
||||||
}
|
}
|
||||||
Page::HelpHelp | Page::HelpStats | Page::HelpAbout | Page::HelpTheme => {
|
Page::HelpHelp | Page::HelpStats | Page::HelpAbout => {
|
||||||
self.open_menu(ctx, SubMenu::Help);
|
self.open_menu(ctx, SubMenu::Help);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -1130,11 +1109,18 @@ impl GossipUi {
|
|||||||
self.add_menu_item_page(ui, Page::HelpHelp, None, true);
|
self.add_menu_item_page(ui, Page::HelpHelp, None, true);
|
||||||
self.add_menu_item_page(ui, Page::HelpStats, None, true);
|
self.add_menu_item_page(ui, Page::HelpStats, None, true);
|
||||||
self.add_menu_item_page(ui, Page::HelpAbout, None, true);
|
self.add_menu_item_page(ui, Page::HelpAbout, None, true);
|
||||||
self.add_menu_item_page(ui, Page::HelpTheme, None, true);
|
|
||||||
});
|
});
|
||||||
self.after_openable_menu(ui, &cstate);
|
self.after_openable_menu(ui, &cstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if self
|
||||||
|
.add_selected_label(ui, self.page == Page::ThemeTest, "Theme Test")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.set_page(ctx, Page::ThemeTest);
|
||||||
|
}
|
||||||
|
|
||||||
// -- Status Area
|
// -- Status Area
|
||||||
ui.with_layout(Layout::bottom_up(Align::LEFT), |ui| {
|
ui.with_layout(Layout::bottom_up(Align::LEFT), |ui| {
|
||||||
notifications::draw_icons(self, ui);
|
notifications::draw_icons(self, ui);
|
||||||
@ -1520,9 +1506,10 @@ impl eframe::App for GossipUi {
|
|||||||
| Page::RelaysKnownNetwork(_) => relays::update(self, ctx, frame, ui),
|
| Page::RelaysKnownNetwork(_) => relays::update(self, ctx, frame, ui),
|
||||||
Page::Search => search::update(self, ctx, frame, ui),
|
Page::Search => search::update(self, ctx, frame, ui),
|
||||||
Page::Settings => settings::update(self, ctx, frame, ui),
|
Page::Settings => settings::update(self, ctx, frame, ui),
|
||||||
Page::HelpHelp | Page::HelpStats | Page::HelpAbout | Page::HelpTheme => {
|
Page::HelpHelp | Page::HelpStats | Page::HelpAbout => {
|
||||||
help::update(self, ctx, frame, ui)
|
help::update(self, ctx, frame, ui)
|
||||||
}
|
}
|
||||||
|
Page::ThemeTest => theme::test_page::update(self, ctx, frame, ui),
|
||||||
Page::Wizard(_) => unreachable!(),
|
Page::Wizard(_) => unreachable!(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -2166,7 +2153,7 @@ fn force_login(app: &mut GossipUi, ctx: &Context) {
|
|||||||
ui.add_space(16.0);
|
ui.add_space(16.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = widgets::TextEdit::singleline(&mut app.password)
|
let output = widgets::TextEdit::singleline(&app.theme,&mut app.password)
|
||||||
.password(true)
|
.password(true)
|
||||||
.with_paste()
|
.with_paste()
|
||||||
.desired_width( 400.0)
|
.desired_width( 400.0)
|
||||||
|
@ -108,7 +108,8 @@ impl<'a> Notification<'a> for AuthRequest {
|
|||||||
});
|
});
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
ui.label("Remember");
|
ui.label("Remember");
|
||||||
widgets::switch_with_size(ui, &mut self.remember, super::SWITCH_SIZE)
|
widgets::Switch::large(theme, &mut self.remember)
|
||||||
|
.show(ui)
|
||||||
.on_hover_text("store permission permanently");
|
.on_hover_text("store permission permanently");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -144,7 +144,8 @@ impl<'a> Notification<'a> for ConnRequest {
|
|||||||
});
|
});
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
ui.label("Remember");
|
ui.label("Remember");
|
||||||
widgets::switch_with_size(ui, &mut self.remember, super::SWITCH_SIZE)
|
widgets::Switch::large(theme, &mut self.remember)
|
||||||
|
.show(ui)
|
||||||
.on_hover_text("store permission permanently");
|
.on_hover_text("store permission permanently");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -55,7 +55,6 @@ pub trait Notification<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NotificationHandle = Rc<RefCell<dyn for<'handle> Notification<'handle>>>;
|
type NotificationHandle = Rc<RefCell<dyn for<'handle> Notification<'handle>>>;
|
||||||
const SWITCH_SIZE: Vec2 = Vec2 { x: 40.0, y: 20.0 };
|
|
||||||
|
|
||||||
pub struct NotificationData {
|
pub struct NotificationData {
|
||||||
active: Vec<NotificationHandle>,
|
active: Vec<NotificationHandle>,
|
||||||
|
@ -131,12 +131,9 @@ impl<'a> Notification<'a> for Nip46Request {
|
|||||||
});
|
});
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
ui.label("Remember");
|
ui.label("Remember");
|
||||||
widgets::switch_with_size(
|
widgets::Switch::large(theme, &mut self.remember)
|
||||||
ui,
|
.show(ui)
|
||||||
&mut self.remember,
|
.on_hover_text("store permission permanently");
|
||||||
super::SWITCH_SIZE,
|
|
||||||
)
|
|
||||||
.on_hover_text("store permission permanently");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -306,7 +306,7 @@ pub(super) fn update(
|
|||||||
// private / public switch
|
// private / public switch
|
||||||
ui.add(Label::new("Private").selectable(false));
|
ui.add(Label::new("Private").selectable(false));
|
||||||
if ui
|
if ui
|
||||||
.add(widgets::Switch::onoff(&app.theme, &mut private))
|
.add(widgets::Switch::small(&app.theme, &mut private))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
let _ = GLOBALS.storage.add_person_to_list(
|
let _ = GLOBALS.storage.add_person_to_list(
|
||||||
@ -424,8 +424,13 @@ fn render_add_contact_popup(
|
|||||||
ui.label("Search for known contacts to add");
|
ui.label("Search for known contacts to add");
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
let mut output =
|
let mut output = widgets::TextEdit::search(
|
||||||
widgets::search_field(ui, &mut app.people_list.add_contact_search, f32::INFINITY);
|
&app.theme,
|
||||||
|
&app.assets,
|
||||||
|
&mut app.people_list.add_contact_search,
|
||||||
|
)
|
||||||
|
.desired_width(f32::INFINITY)
|
||||||
|
.show(ui);
|
||||||
|
|
||||||
let mut selected = app.people_list.add_contact_search_selected;
|
let mut selected = app.people_list.add_contact_search_selected;
|
||||||
widgets::show_contact_search(
|
widgets::show_contact_search(
|
||||||
@ -611,7 +616,7 @@ pub(super) fn render_create_list_dialog(ui: &mut Ui, app: &mut GossipUi) {
|
|||||||
}
|
}
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add(widgets::Switch::onoff(
|
ui.add(widgets::Switch::small(
|
||||||
&app.theme,
|
&app.theme,
|
||||||
&mut app.new_list_favorite,
|
&mut app.new_list_favorite,
|
||||||
));
|
));
|
||||||
|
@ -203,7 +203,7 @@ fn content(app: &mut GossipUi, ctx: &Context, ui: &mut Ui, pubkey: PublicKey, pe
|
|||||||
let mut inlist = membership.is_some();
|
let mut inlist = membership.is_some();
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
.add(widgets::Switch::onoff(&app.theme, &mut inlist))
|
.add(widgets::Switch::small(&app.theme, &mut inlist))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
if !inlist {
|
if !inlist {
|
||||||
@ -228,7 +228,7 @@ fn content(app: &mut GossipUi, ctx: &Context, ui: &mut Ui, pubkey: PublicKey, pe
|
|||||||
|
|
||||||
let mut private = !membership.unwrap_or(&false);
|
let mut private = !membership.unwrap_or(&false);
|
||||||
let switch_response =
|
let switch_response =
|
||||||
ui.add(widgets::Switch::onoff(&app.theme, &mut private));
|
ui.add(widgets::Switch::small(&app.theme, &mut private));
|
||||||
if switch_response.clicked() {
|
if switch_response.clicked() {
|
||||||
let _ = GLOBALS
|
let _ = GLOBALS
|
||||||
.storage
|
.storage
|
||||||
|
@ -20,7 +20,9 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
btn_h_space!(ui);
|
btn_h_space!(ui);
|
||||||
super::relay_sort_combo(app, ui);
|
super::relay_sort_combo(app, ui);
|
||||||
btn_h_space!(ui);
|
btn_h_space!(ui);
|
||||||
widgets::search_field(ui, &mut app.relays.search, 200.0);
|
widgets::TextEdit::search(&app.theme, &app.assets, &mut app.relays.search)
|
||||||
|
.desired_width(200.0)
|
||||||
|
.show(ui);
|
||||||
ui.add_space(200.0); // search_field somehow doesn't "take up" space
|
ui.add_space(200.0); // search_field somehow doesn't "take up" space
|
||||||
if ui
|
if ui
|
||||||
.button(RichText::new(Page::RelaysCoverage.name()))
|
.button(RichText::new(Page::RelaysCoverage.name()))
|
||||||
|
@ -16,7 +16,9 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
|
|||||||
btn_h_space!(ui);
|
btn_h_space!(ui);
|
||||||
super::relay_sort_combo(app, ui);
|
super::relay_sort_combo(app, ui);
|
||||||
btn_h_space!(ui);
|
btn_h_space!(ui);
|
||||||
widgets::search_field(ui, &mut app.relays.search, 200.0);
|
widgets::TextEdit::search(&app.theme, &app.assets, &mut app.relays.search)
|
||||||
|
.desired_width(200.0)
|
||||||
|
.show(ui);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TBD time how long this takes. We don't want expensive code in the UI
|
// TBD time how long this takes. We don't want expensive code in the UI
|
||||||
|
@ -17,7 +17,9 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
|
|||||||
btn_h_space!(ui);
|
btn_h_space!(ui);
|
||||||
super::relay_sort_combo(app, ui);
|
super::relay_sort_combo(app, ui);
|
||||||
btn_h_space!(ui);
|
btn_h_space!(ui);
|
||||||
widgets::search_field(ui, &mut app.relays.search, 200.0);
|
widgets::TextEdit::search(&app.theme, &app.assets, &mut app.relays.search)
|
||||||
|
.desired_width(200.0)
|
||||||
|
.show(ui);
|
||||||
ui.add_space(200.0); // search_field somehow doesn't "take up" space
|
ui.add_space(200.0); // search_field somehow doesn't "take up" space
|
||||||
widgets::set_important_button_visuals(ui, app);
|
widgets::set_important_button_visuals(ui, app);
|
||||||
if ui.button("Advertise Relay List")
|
if ui.button("Advertise Relay List")
|
||||||
|
@ -473,17 +473,21 @@ pub(super) fn configure_list_btn(app: &mut GossipUi, ui: &mut Ui) {
|
|||||||
.with_min_size(min_size)
|
.with_min_size(min_size)
|
||||||
.with_hover_text("Configure List View".to_owned())
|
.with_hover_text("Configure List View".to_owned())
|
||||||
.show(ui, |ui, is_open| {
|
.show(ui, |ui, is_open| {
|
||||||
let size = ui.spacing().interact_size.y * egui::vec2(1.6, 0.8);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if widgets::switch_with_size(ui, &mut app.relays.show_details, size).changed() {
|
if widgets::Switch::small(&app.theme, &mut app.relays.show_details)
|
||||||
|
.show(ui)
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
*is_open = false;
|
*is_open = false;
|
||||||
}
|
}
|
||||||
ui.label("Show details");
|
ui.label("Show details");
|
||||||
});
|
});
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if widgets::switch_with_size(ui, &mut app.relays.show_hidden, size).changed() {
|
if widgets::Switch::small(&app.theme, &mut app.relays.show_hidden)
|
||||||
|
.show(ui)
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
*is_open = false;
|
*is_open = false;
|
||||||
}
|
}
|
||||||
ui.label("Show hidden relays");
|
ui.label("Show hidden relays");
|
||||||
|
@ -11,6 +11,8 @@ use std::collections::BTreeMap;
|
|||||||
mod default;
|
mod default;
|
||||||
pub use default::DefaultTheme;
|
pub use default::DefaultTheme;
|
||||||
|
|
||||||
|
pub(super) mod test_page;
|
||||||
|
|
||||||
pub fn apply_theme(theme: &Theme, ctx: &Context) {
|
pub fn apply_theme(theme: &Theme, ctx: &Context) {
|
||||||
ctx.set_style(theme.get_style());
|
ctx.set_style(theme.get_style());
|
||||||
ctx.set_fonts(theme.font_definitions());
|
ctx.set_fonts(theme.font_definitions());
|
||||||
|
441
gossip-bin/src/ui/theme/test_page.rs
Normal file
441
gossip-bin/src/ui/theme/test_page.rs
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
use crate::ui::feed::NoteRenderData;
|
||||||
|
use crate::ui::widgets;
|
||||||
|
use crate::ui::GossipUi;
|
||||||
|
use crate::ui::HighlightType;
|
||||||
|
use eframe::egui;
|
||||||
|
use egui::text::LayoutJob;
|
||||||
|
use egui::widget_text::WidgetText;
|
||||||
|
use egui::{Color32, Context, Frame, Margin, RichText, Ui};
|
||||||
|
use egui_winit::egui::Vec2;
|
||||||
|
use egui_winit::egui::Widget;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum Background {
|
||||||
|
None,
|
||||||
|
Input,
|
||||||
|
Note,
|
||||||
|
HighlightedNote,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ThemeTest {
|
||||||
|
textedit_empty: String,
|
||||||
|
textedit_filled: String,
|
||||||
|
switch_value: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ThemeTest {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
textedit_empty: Default::default(),
|
||||||
|
textedit_filled: "Some text".into(),
|
||||||
|
switch_value: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::ui) fn update(
|
||||||
|
app: &mut GossipUi,
|
||||||
|
_ctx: &Context,
|
||||||
|
_frame: &mut eframe::Frame,
|
||||||
|
ui: &mut Ui,
|
||||||
|
) {
|
||||||
|
widgets::page_header(ui, "Theme Test", |_ui| {});
|
||||||
|
|
||||||
|
app.vert_scroll_area()
|
||||||
|
.id_source(ui.auto_id_with("theme_test"))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
button_test(app, ui);
|
||||||
|
|
||||||
|
ui.add_space(20.0);
|
||||||
|
|
||||||
|
textedit_test(app, ui);
|
||||||
|
|
||||||
|
ui.add_space(20.0);
|
||||||
|
|
||||||
|
switch_test(app, ui);
|
||||||
|
|
||||||
|
ui.add_space(20.0);
|
||||||
|
|
||||||
|
align_test(app, ui);
|
||||||
|
|
||||||
|
ui.add_space(20.0);
|
||||||
|
|
||||||
|
// On No Background
|
||||||
|
Frame::none()
|
||||||
|
.inner_margin(Margin::symmetric(20.0, 20.0))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.heading("No Background");
|
||||||
|
inner(app, ui, Background::None);
|
||||||
|
});
|
||||||
|
|
||||||
|
// On Note Background
|
||||||
|
let render_data = NoteRenderData {
|
||||||
|
height: 200.0,
|
||||||
|
is_new: false,
|
||||||
|
is_main_event: false,
|
||||||
|
has_repost: false,
|
||||||
|
is_comment_mention: false,
|
||||||
|
is_thread: false,
|
||||||
|
is_first: true,
|
||||||
|
is_last: true,
|
||||||
|
thread_position: 0,
|
||||||
|
};
|
||||||
|
Frame::none()
|
||||||
|
.inner_margin(app.theme.feed_frame_inner_margin(&render_data))
|
||||||
|
.outer_margin(app.theme.feed_frame_outer_margin(&render_data))
|
||||||
|
.rounding(app.theme.feed_frame_rounding(&render_data))
|
||||||
|
.shadow(app.theme.feed_frame_shadow(&render_data))
|
||||||
|
.fill(app.theme.feed_frame_fill(&render_data))
|
||||||
|
.stroke(app.theme.feed_frame_stroke(&render_data))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.heading("Note Background");
|
||||||
|
ui.label("(with note margins)");
|
||||||
|
inner(app, ui, Background::Note);
|
||||||
|
});
|
||||||
|
|
||||||
|
// On Highlighted Note Background
|
||||||
|
let render_data = NoteRenderData {
|
||||||
|
height: 200.0,
|
||||||
|
is_new: true,
|
||||||
|
is_main_event: false,
|
||||||
|
has_repost: false,
|
||||||
|
is_comment_mention: false,
|
||||||
|
is_thread: false,
|
||||||
|
is_first: true,
|
||||||
|
is_last: true,
|
||||||
|
thread_position: 0,
|
||||||
|
};
|
||||||
|
Frame::none()
|
||||||
|
.inner_margin(app.theme.feed_frame_inner_margin(&render_data))
|
||||||
|
.outer_margin(app.theme.feed_frame_outer_margin(&render_data))
|
||||||
|
.rounding(app.theme.feed_frame_rounding(&render_data))
|
||||||
|
.shadow(app.theme.feed_frame_shadow(&render_data))
|
||||||
|
.fill(app.theme.feed_frame_fill(&render_data))
|
||||||
|
.stroke(app.theme.feed_frame_stroke(&render_data))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.heading("Unread Note Background");
|
||||||
|
ui.label("(with note margins)");
|
||||||
|
inner(app, ui, Background::HighlightedNote);
|
||||||
|
});
|
||||||
|
|
||||||
|
// On Input Background
|
||||||
|
Frame::none()
|
||||||
|
.fill(app.theme.get_style().visuals.extreme_bg_color)
|
||||||
|
.inner_margin(Margin::symmetric(20.0, 20.0))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.heading("Input Background");
|
||||||
|
inner(app, ui, Background::Input);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner(app: &mut GossipUi, ui: &mut Ui, background: Background) {
|
||||||
|
let theme = app.theme;
|
||||||
|
let accent = RichText::new("accent").color(theme.accent_color());
|
||||||
|
let accent_complementary = RichText::new("accent complimentary (indirectly used)")
|
||||||
|
.color(theme.accent_complementary_color());
|
||||||
|
|
||||||
|
line(ui, accent);
|
||||||
|
line(ui, accent_complementary);
|
||||||
|
|
||||||
|
if background == Background::Input {
|
||||||
|
for (ht, txt) in [
|
||||||
|
(HighlightType::Nothing, "nothing"),
|
||||||
|
(HighlightType::PublicKey, "public key"),
|
||||||
|
(HighlightType::Event, "event"),
|
||||||
|
(HighlightType::Relay, "relay"),
|
||||||
|
(HighlightType::Hyperlink, "hyperlink"),
|
||||||
|
] {
|
||||||
|
let mut highlight_job = LayoutJob::default();
|
||||||
|
highlight_job.append(
|
||||||
|
&format!("highlight text format for {}", txt),
|
||||||
|
0.0,
|
||||||
|
theme.highlight_text_format(ht),
|
||||||
|
);
|
||||||
|
line(ui, WidgetText::LayoutJob(highlight_job));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if background == Background::Note || background == Background::HighlightedNote {
|
||||||
|
let warning_marker =
|
||||||
|
RichText::new("warning marker").color(theme.warning_marker_text_color());
|
||||||
|
line(ui, warning_marker);
|
||||||
|
|
||||||
|
let notice_marker = RichText::new("notice marker").color(theme.notice_marker_text_color());
|
||||||
|
line(ui, notice_marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
if background != Background::Input {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(RichText::new("•").color(Color32::from_gray(128)));
|
||||||
|
crate::ui::widgets::break_anywhere_hyperlink_to(
|
||||||
|
ui,
|
||||||
|
"https://hyperlink.example.com",
|
||||||
|
"https://hyperlink.example.com",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line(ui: &mut Ui, label: impl Into<WidgetText>) {
|
||||||
|
let bullet = RichText::new("•").color(Color32::from_gray(128));
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(bullet);
|
||||||
|
ui.label(label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button_test(app: &mut GossipUi, ui: &mut Ui) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.heading("Button Test:");
|
||||||
|
ui.add_space(30.0);
|
||||||
|
});
|
||||||
|
ui.add_space(30.0);
|
||||||
|
const TEXT: &str = "Continue";
|
||||||
|
let theme = &app.theme;
|
||||||
|
const CSIZE: Vec2 = Vec2 { x: 100.0, y: 20.0 };
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("Default"));
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::primary(theme, TEXT).draw_default(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::secondary(theme, TEXT).draw_default(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::bordered(theme, TEXT).draw_default(ui);
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("Hovered"));
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::primary(theme, TEXT).draw_hovered(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::secondary(theme, TEXT).draw_hovered(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::bordered(theme, TEXT).draw_hovered(ui);
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("Active"));
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::primary(theme, TEXT).draw_active(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::secondary(theme, TEXT).draw_active(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::bordered(theme, TEXT).draw_active(ui);
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("Disabled"));
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::primary(theme, TEXT).draw_disabled(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::secondary(theme, TEXT).draw_disabled(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::bordered(theme, TEXT).draw_disabled(ui);
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("Focused"));
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::primary(theme, TEXT).draw_focused(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::secondary(theme, TEXT).draw_focused(ui);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
widgets::Button::bordered(theme, TEXT).draw_focused(ui);
|
||||||
|
});
|
||||||
|
ui.add_space(30.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("try it->"));
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let response = widgets::Button::primary(theme, TEXT).ui(ui);
|
||||||
|
if ui.link("focus").clicked() {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let response = widgets::Button::secondary(theme, TEXT).ui(ui);
|
||||||
|
if ui.link("focus").clicked() {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let response = widgets::Button::bordered(theme, TEXT).ui(ui);
|
||||||
|
if ui.link("focus").clicked() {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn textedit_test(app: &mut GossipUi, ui: &mut Ui) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.heading("Button Test:");
|
||||||
|
ui.add_space(30.0);
|
||||||
|
});
|
||||||
|
ui.add_space(30.0);
|
||||||
|
let theme = &app.theme;
|
||||||
|
let assets = &app.assets;
|
||||||
|
const HINT: &str = "Placeholder";
|
||||||
|
const CSIZE: Vec2 = Vec2 { x: 100.0, y: 20.0 };
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("Empty"));
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let output =
|
||||||
|
widgets::TextEdit::singleline(theme, &mut app.theme_test.textedit_empty)
|
||||||
|
.hint_text(HINT)
|
||||||
|
.show(ui);
|
||||||
|
if ui.link("focus").clicked() {
|
||||||
|
output.response.request_focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let output =
|
||||||
|
widgets::TextEdit::search(theme, assets, &mut app.theme_test.textedit_empty)
|
||||||
|
.hint_text(HINT)
|
||||||
|
.show(ui);
|
||||||
|
if ui.link("focus").clicked() {
|
||||||
|
output.response.request_focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("with Text"));
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let output =
|
||||||
|
widgets::TextEdit::singleline(theme, &mut app.theme_test.textedit_filled)
|
||||||
|
.hint_text(HINT)
|
||||||
|
.show(ui);
|
||||||
|
if ui.link("focus").clicked() {
|
||||||
|
output.response.request_focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let output =
|
||||||
|
widgets::TextEdit::search(theme, assets, &mut app.theme_test.textedit_filled)
|
||||||
|
.hint_text(HINT)
|
||||||
|
.show(ui);
|
||||||
|
if ui.link("focus").clicked() {
|
||||||
|
output.response.request_focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("Disabled"));
|
||||||
|
ui.set_enabled(false);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
widgets::TextEdit::singleline(theme, &mut app.theme_test.textedit_empty)
|
||||||
|
.hint_text(HINT)
|
||||||
|
.show(ui);
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
widgets::TextEdit::search(theme, assets, &mut app.theme_test.textedit_empty)
|
||||||
|
.hint_text(HINT)
|
||||||
|
.show(ui);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_test(app: &mut GossipUi, ui: &mut Ui) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.heading("Switch Test:");
|
||||||
|
ui.add_space(30.0);
|
||||||
|
});
|
||||||
|
ui.add_space(30.0);
|
||||||
|
let theme = &app.theme;
|
||||||
|
const TEXT: &str = "Some text";
|
||||||
|
const CSIZE: Vec2 = Vec2 { x: 100.0, y: 20.0 };
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("Enabled"));
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let response = ui
|
||||||
|
.horizontal(|ui| {
|
||||||
|
let response = ui.add(
|
||||||
|
widgets::Switch::small(theme, &mut app.theme_test.switch_value)
|
||||||
|
.with_label(TEXT),
|
||||||
|
);
|
||||||
|
response
|
||||||
|
})
|
||||||
|
.inner;
|
||||||
|
if ui.link("focus").clicked() {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let response = ui
|
||||||
|
.horizontal(|ui| {
|
||||||
|
let response = ui.add(
|
||||||
|
widgets::Switch::large(theme, &mut app.theme_test.switch_value)
|
||||||
|
.with_label(TEXT),
|
||||||
|
);
|
||||||
|
response
|
||||||
|
})
|
||||||
|
.inner;
|
||||||
|
if ui.link("focus").clicked() {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_sized(CSIZE, egui::Label::new("Disabled"));
|
||||||
|
ui.set_enabled(false);
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(widgets::Switch::small(theme, &mut false).with_label(TEXT));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.add_space(20.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(widgets::Switch::large(theme, &mut false).with_label(TEXT));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn align_test(app: &mut GossipUi, ui: &mut Ui) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.heading("Horizontal Alignment Test:");
|
||||||
|
ui.add_space(30.0);
|
||||||
|
});
|
||||||
|
ui.add_space(30.0);
|
||||||
|
let theme = &app.theme;
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Text");
|
||||||
|
widgets::Button::primary(theme, "Primary").show(ui);
|
||||||
|
ui.label("text");
|
||||||
|
widgets::TextEdit::singleline(theme, &mut app.theme_test.textedit_filled)
|
||||||
|
.desired_width(100.0)
|
||||||
|
.show(ui);
|
||||||
|
ui.label("text");
|
||||||
|
widgets::Switch::small(theme, &mut app.theme_test.switch_value)
|
||||||
|
.with_label("Switch")
|
||||||
|
.show(ui);
|
||||||
|
egui::ComboBox::from_label("Select").show_ui(ui, |ui| {
|
||||||
|
let _ = ui.selectable_label(false, "first");
|
||||||
|
let _ = ui.selectable_label(false, "second");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,16 +1,30 @@
|
|||||||
use egui_winit::egui::{self, Response, Ui, Widget, WidgetText};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::super::Theme;
|
use egui_winit::egui::{
|
||||||
|
self, vec2, Galley, NumExt, Rect, Response, Rounding, Sense, Stroke, TextStyle, Ui, Vec2,
|
||||||
|
Widget, WidgetInfo, WidgetText, WidgetType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{super::Theme, WidgetState};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
enum ButtonType {
|
enum ButtonType {
|
||||||
Primary,
|
Primary,
|
||||||
Secondary,
|
Secondary,
|
||||||
|
Bordered,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ButtonVariant {
|
||||||
|
Normal,
|
||||||
|
// Small,
|
||||||
|
// Wide,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clickable button with text
|
/// Clickable button with text
|
||||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||||
pub struct Button<'a> {
|
pub struct Button<'a> {
|
||||||
button_type: ButtonType,
|
button_type: ButtonType,
|
||||||
|
variant: ButtonVariant,
|
||||||
theme: &'a Theme,
|
theme: &'a Theme,
|
||||||
text: Option<WidgetText>,
|
text: Option<WidgetText>,
|
||||||
}
|
}
|
||||||
@ -19,6 +33,7 @@ impl<'a> Button<'a> {
|
|||||||
pub fn primary(theme: &'a Theme, text: impl Into<WidgetText>) -> Self {
|
pub fn primary(theme: &'a Theme, text: impl Into<WidgetText>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
button_type: ButtonType::Primary,
|
button_type: ButtonType::Primary,
|
||||||
|
variant: ButtonVariant::Normal,
|
||||||
theme,
|
theme,
|
||||||
text: Some(text.into()),
|
text: Some(text.into()),
|
||||||
}
|
}
|
||||||
@ -27,19 +42,423 @@ impl<'a> Button<'a> {
|
|||||||
pub fn secondary(theme: &'a Theme, text: impl Into<WidgetText>) -> Self {
|
pub fn secondary(theme: &'a Theme, text: impl Into<WidgetText>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
button_type: ButtonType::Secondary,
|
button_type: ButtonType::Secondary,
|
||||||
|
variant: ButtonVariant::Normal,
|
||||||
theme,
|
theme,
|
||||||
text: Some(text.into()),
|
text: Some(text.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bordered(theme: &'a Theme, text: impl Into<WidgetText>) -> Self {
|
||||||
|
Self {
|
||||||
|
button_type: ButtonType::Bordered,
|
||||||
|
variant: ButtonVariant::Normal,
|
||||||
|
theme,
|
||||||
|
text: Some(text.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// Make this a small button, suitable for embedding into text.
|
||||||
|
// pub fn small(mut self, small: bool) -> Self {
|
||||||
|
// if small {
|
||||||
|
// self.variant = ButtonVariant::Small;
|
||||||
|
// }
|
||||||
|
// self
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Make this a wide button.
|
||||||
|
// pub fn wide(mut self, wide: bool) -> Self {
|
||||||
|
// if wide {
|
||||||
|
// self.variant = ButtonVariant::Wide;
|
||||||
|
// }
|
||||||
|
// self
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn draw_default(self, ui: &mut Ui) -> Response {
|
||||||
|
let (text, desired_size, padding) = Self::layout(ui, self.text, self.variant);
|
||||||
|
let (rect, response) = Self::allocate(ui, &text, desired_size);
|
||||||
|
Self::draw(
|
||||||
|
ui,
|
||||||
|
text,
|
||||||
|
rect,
|
||||||
|
WidgetState::Default,
|
||||||
|
self.button_type,
|
||||||
|
padding,
|
||||||
|
self.theme,
|
||||||
|
);
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_hovered(self, ui: &mut Ui) -> Response {
|
||||||
|
let (text, desired_size, padding) = Self::layout(ui, self.text, self.variant);
|
||||||
|
let (rect, response) = Self::allocate(ui, &text, desired_size);
|
||||||
|
Self::draw(
|
||||||
|
ui,
|
||||||
|
text,
|
||||||
|
rect,
|
||||||
|
WidgetState::Hovered,
|
||||||
|
self.button_type,
|
||||||
|
padding,
|
||||||
|
self.theme,
|
||||||
|
);
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_active(self, ui: &mut Ui) -> Response {
|
||||||
|
let (text, desired_size, padding) = Self::layout(ui, self.text, self.variant);
|
||||||
|
let (rect, response) = Self::allocate(ui, &text, desired_size);
|
||||||
|
Self::draw(
|
||||||
|
ui,
|
||||||
|
text,
|
||||||
|
rect,
|
||||||
|
WidgetState::Active,
|
||||||
|
self.button_type,
|
||||||
|
padding,
|
||||||
|
self.theme,
|
||||||
|
);
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_disabled(self, ui: &mut Ui) -> Response {
|
||||||
|
let (text, desired_size, padding) = Self::layout(ui, self.text, self.variant);
|
||||||
|
let (rect, response) = Self::allocate(ui, &text, desired_size);
|
||||||
|
Self::draw(
|
||||||
|
ui,
|
||||||
|
text,
|
||||||
|
rect,
|
||||||
|
WidgetState::Disabled,
|
||||||
|
self.button_type,
|
||||||
|
padding,
|
||||||
|
self.theme,
|
||||||
|
);
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_focused(self, ui: &mut Ui) -> Response {
|
||||||
|
let (text, desired_size, padding) = Self::layout(ui, self.text, self.variant);
|
||||||
|
let (rect, response) = Self::allocate(ui, &text, desired_size);
|
||||||
|
Self::draw(
|
||||||
|
ui,
|
||||||
|
text,
|
||||||
|
rect,
|
||||||
|
WidgetState::Focused,
|
||||||
|
self.button_type,
|
||||||
|
padding,
|
||||||
|
self.theme,
|
||||||
|
);
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(self, ui: &mut Ui) -> Response {
|
||||||
|
let (text, desired_size, padding) = Self::layout(ui, self.text, self.variant);
|
||||||
|
let (rect, response) = Self::allocate(ui, &text, desired_size);
|
||||||
|
let state = if response.is_pointer_button_down_on() {
|
||||||
|
WidgetState::Active
|
||||||
|
} else if response.has_focus() {
|
||||||
|
WidgetState::Focused
|
||||||
|
} else if response.hovered() || response.highlighted() {
|
||||||
|
WidgetState::Hovered
|
||||||
|
} else if !ui.is_enabled() {
|
||||||
|
WidgetState::Disabled
|
||||||
|
} else {
|
||||||
|
WidgetState::Default
|
||||||
|
};
|
||||||
|
Self::draw(ui, text, rect, state, self.button_type, padding, self.theme);
|
||||||
|
response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Button<'_> {
|
impl Widget for Button<'_> {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
match self.button_type {
|
self.show(ui)
|
||||||
ButtonType::Primary => self.theme.primary_button_style(ui.style_mut()),
|
}
|
||||||
ButtonType::Secondary => self.theme.secondary_button_style(ui.style_mut()),
|
}
|
||||||
}
|
|
||||||
let button = egui::Button::opt_image_and_text(None, self.text);
|
impl Button<'_> {
|
||||||
button.ui(ui)
|
fn layout(
|
||||||
|
ui: &mut Ui,
|
||||||
|
text: Option<WidgetText>,
|
||||||
|
variant: ButtonVariant,
|
||||||
|
) -> (Option<Arc<Galley>>, Vec2, Vec2) {
|
||||||
|
let frame = ui.visuals().button_frame;
|
||||||
|
|
||||||
|
let button_padding = if frame {
|
||||||
|
Vec2::new(14.0, 5.0)
|
||||||
|
} else {
|
||||||
|
Vec2::ZERO
|
||||||
|
};
|
||||||
|
|
||||||
|
// match variant {
|
||||||
|
// ButtonVariant::Normal => {}
|
||||||
|
// ButtonVariant::Small => {
|
||||||
|
// button_padding.y = 0.0;
|
||||||
|
// }
|
||||||
|
// ButtonVariant::Wide => {
|
||||||
|
// button_padding.x *= 3.0;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
let wrap = None;
|
||||||
|
let text_wrap_width = ui.available_width() - 2.0 * button_padding.x;
|
||||||
|
|
||||||
|
let text = text.map(|text| text.into_galley(ui, wrap, text_wrap_width, TextStyle::Button));
|
||||||
|
|
||||||
|
let mut desired_size = Vec2::ZERO;
|
||||||
|
if let Some(text) = &text {
|
||||||
|
desired_size.x += text.size().x;
|
||||||
|
desired_size.y = desired_size.y.max(text.size().y);
|
||||||
|
}
|
||||||
|
desired_size += 2.0 * button_padding;
|
||||||
|
match variant {
|
||||||
|
ButtonVariant::Normal => {
|
||||||
|
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||||
|
} // ButtonVariant::Wide | ButtonVariant::Small => {}
|
||||||
|
}
|
||||||
|
(text, desired_size, button_padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate(ui: &mut Ui, text: &Option<Arc<Galley>>, desired_size: Vec2) -> (Rect, Response) {
|
||||||
|
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click());
|
||||||
|
response.widget_info(|| {
|
||||||
|
if let Some(text) = text {
|
||||||
|
WidgetInfo::labeled(WidgetType::Button, text.text())
|
||||||
|
} else {
|
||||||
|
WidgetInfo::new(WidgetType::Button)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(cursor) = ui.visuals().interact_cursor {
|
||||||
|
if response.hovered {
|
||||||
|
ui.ctx().set_cursor_icon(cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(rect, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
ui: &mut Ui,
|
||||||
|
text: Option<Arc<Galley>>,
|
||||||
|
rect: Rect,
|
||||||
|
state: WidgetState,
|
||||||
|
button_type: ButtonType,
|
||||||
|
button_padding: Vec2,
|
||||||
|
theme: &Theme,
|
||||||
|
) {
|
||||||
|
if ui.is_rect_visible(rect) {
|
||||||
|
let no_stroke = Stroke::NONE;
|
||||||
|
let neutral_300_stroke = Stroke::new(1.0, theme.neutral_300());
|
||||||
|
let neutral_400_stroke = Stroke::new(1.0, theme.neutral_400());
|
||||||
|
let neutral_500_stroke = Stroke::new(1.0, theme.neutral_500());
|
||||||
|
let neutral_600_stroke = Stroke::new(1.0, theme.neutral_600());
|
||||||
|
let (frame_fill, frame_stroke, text_color, under_stroke) = if ui.visuals().dark_mode {
|
||||||
|
match state {
|
||||||
|
WidgetState::Default => match button_type {
|
||||||
|
ButtonType::Primary => (
|
||||||
|
theme.accent_dark(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_50(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Secondary => (
|
||||||
|
theme.neutral_200(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_700(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Bordered => (
|
||||||
|
theme.neutral_950(),
|
||||||
|
neutral_400_stroke,
|
||||||
|
theme.neutral_300(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
WidgetState::Hovered => match button_type {
|
||||||
|
ButtonType::Primary => (
|
||||||
|
theme.accent_dark_b20(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_50(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Secondary => (
|
||||||
|
theme.neutral_50(),
|
||||||
|
no_stroke,
|
||||||
|
theme.accent_dark(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Bordered => (
|
||||||
|
theme.neutral_950(),
|
||||||
|
neutral_300_stroke,
|
||||||
|
theme.neutral_200(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
WidgetState::Active => match button_type {
|
||||||
|
ButtonType::Primary => (
|
||||||
|
theme.accent_dark(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_50(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Secondary => (
|
||||||
|
theme.neutral_200(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_700(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Bordered => (
|
||||||
|
theme.neutral_950(),
|
||||||
|
neutral_400_stroke,
|
||||||
|
theme.neutral_300(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
WidgetState::Disabled => (
|
||||||
|
theme.neutral_700(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_500(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
WidgetState::Focused => match button_type {
|
||||||
|
ButtonType::Primary => (
|
||||||
|
theme.accent_dark_b20(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_50(),
|
||||||
|
neutral_300_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Secondary => (
|
||||||
|
theme.neutral_50(),
|
||||||
|
no_stroke,
|
||||||
|
theme.accent_dark(),
|
||||||
|
neutral_400_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Bordered => (
|
||||||
|
theme.neutral_950(),
|
||||||
|
neutral_300_stroke,
|
||||||
|
theme.neutral_200(),
|
||||||
|
neutral_500_stroke,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match state {
|
||||||
|
WidgetState::Default => match button_type {
|
||||||
|
ButtonType::Primary => (
|
||||||
|
theme.accent_light(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_50(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Secondary => (
|
||||||
|
theme.neutral_700(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_100(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Bordered => (
|
||||||
|
theme.neutral_100(),
|
||||||
|
neutral_500_stroke,
|
||||||
|
theme.neutral_800(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
WidgetState::Hovered => match button_type {
|
||||||
|
ButtonType::Primary => (
|
||||||
|
theme.accent_light_b20(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_50(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Secondary => (
|
||||||
|
theme.neutral_900(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_100(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Bordered => (
|
||||||
|
theme.neutral_50(),
|
||||||
|
neutral_600_stroke,
|
||||||
|
theme.neutral_800(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
WidgetState::Active => match button_type {
|
||||||
|
ButtonType::Primary => (
|
||||||
|
theme.accent_light(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_50(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Secondary => (
|
||||||
|
theme.neutral_700(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_100(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Bordered => (
|
||||||
|
theme.neutral_100(),
|
||||||
|
neutral_600_stroke,
|
||||||
|
theme.accent_light(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
WidgetState::Disabled => (
|
||||||
|
theme.neutral_300(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_400(),
|
||||||
|
no_stroke,
|
||||||
|
),
|
||||||
|
WidgetState::Focused => match button_type {
|
||||||
|
ButtonType::Primary => (
|
||||||
|
theme.accent_light_b20(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_50(),
|
||||||
|
neutral_300_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Secondary => (
|
||||||
|
theme.neutral_900(),
|
||||||
|
no_stroke,
|
||||||
|
theme.neutral_100(),
|
||||||
|
neutral_400_stroke,
|
||||||
|
),
|
||||||
|
ButtonType::Bordered => (
|
||||||
|
theme.neutral_50(),
|
||||||
|
neutral_600_stroke,
|
||||||
|
theme.neutral_800(),
|
||||||
|
neutral_400_stroke,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let shrink = Vec2::splat(frame_stroke.width / 2.0);
|
||||||
|
ui.painter().rect(
|
||||||
|
rect.shrink2(shrink),
|
||||||
|
Rounding::same(4.0),
|
||||||
|
frame_fill,
|
||||||
|
frame_stroke,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(galley) = text {
|
||||||
|
let text_pos = {
|
||||||
|
// Make sure button text is centered if within a centered layout
|
||||||
|
ui.layout()
|
||||||
|
.align_size_within_rect(galley.size(), rect.shrink2(button_padding))
|
||||||
|
.min
|
||||||
|
};
|
||||||
|
let painter = ui.painter();
|
||||||
|
painter.galley(text_pos, galley.clone(), text_color);
|
||||||
|
let text_rect = Rect::from_min_size(text_pos, galley.rect.size());
|
||||||
|
let shapes = egui::Shape::dashed_line(
|
||||||
|
&[
|
||||||
|
text_rect.left_bottom() + vec2(0.0, 0.0),
|
||||||
|
text_rect.right_bottom() + vec2(0.0, 0.0),
|
||||||
|
],
|
||||||
|
under_stroke,
|
||||||
|
3.0,
|
||||||
|
3.0,
|
||||||
|
);
|
||||||
|
painter.add(shapes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,8 @@ pub use copy_button::{CopyButton, COPY_SYMBOL_SIZE};
|
|||||||
mod nav_item;
|
mod nav_item;
|
||||||
use eframe::egui::{FontId, Galley};
|
use eframe::egui::{FontId, Galley};
|
||||||
use egui_winit::egui::text::LayoutJob;
|
use egui_winit::egui::text::LayoutJob;
|
||||||
use egui_winit::egui::text_edit::TextEditOutput;
|
|
||||||
use egui_winit::egui::{
|
use egui_winit::egui::{
|
||||||
self, vec2, Align, FontSelection, Rect, Response, RichText, Rounding, Sense, Ui, WidgetText,
|
self, Align, FontSelection, Response, RichText, Rounding, Sense, Ui, WidgetText,
|
||||||
};
|
};
|
||||||
pub use nav_item::NavItem;
|
pub use nav_item::NavItem;
|
||||||
|
|
||||||
@ -37,8 +36,8 @@ pub use information_popup::InformationPopup;
|
|||||||
pub use information_popup::ProfilePopup;
|
pub use information_popup::ProfilePopup;
|
||||||
|
|
||||||
mod switch;
|
mod switch;
|
||||||
|
pub use switch::switch_custom_at;
|
||||||
pub use switch::Switch;
|
pub use switch::Switch;
|
||||||
pub use switch::{switch_custom_at, switch_with_size};
|
|
||||||
|
|
||||||
mod textedit;
|
mod textedit;
|
||||||
pub use textedit::TextEdit;
|
pub use textedit::TextEdit;
|
||||||
@ -48,17 +47,13 @@ use super::{GossipUi, Theme};
|
|||||||
pub const DROPDOWN_DISTANCE: f32 = 10.0;
|
pub const DROPDOWN_DISTANCE: f32 = 10.0;
|
||||||
pub const TAGG_WIDTH: f32 = 200.0;
|
pub const TAGG_WIDTH: f32 = 200.0;
|
||||||
|
|
||||||
// pub fn break_anywhere_label(ui: &mut Ui, text: impl Into<WidgetText>) {
|
pub enum WidgetState {
|
||||||
// let mut job = text.into().into_text_job(
|
Default,
|
||||||
// ui.style(),
|
Hovered,
|
||||||
// FontSelection::Default,
|
Active,
|
||||||
// ui.layout().vertical_align(),
|
Disabled,
|
||||||
// );
|
Focused,
|
||||||
// job.job.sections.first_mut().unwrap().format.color =
|
}
|
||||||
// ui.visuals().widgets.noninteractive.fg_stroke.color;
|
|
||||||
// job.job.wrap.break_anywhere = true;
|
|
||||||
// ui.label(job.job);
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn page_header<R>(
|
pub fn page_header<R>(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
@ -167,36 +162,6 @@ pub fn break_anywhere_hyperlink_to(ui: &mut Ui, text: impl Into<WidgetText>, url
|
|||||||
ui.hyperlink_to(job, url);
|
ui.hyperlink_to(job, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_field(ui: &mut Ui, field: &mut String, width: f32) -> TextEditOutput {
|
|
||||||
// search field
|
|
||||||
let output = TextEdit::singleline(field)
|
|
||||||
.text_color(ui.visuals().widgets.inactive.fg_stroke.color)
|
|
||||||
.desired_width(width)
|
|
||||||
.show(ui);
|
|
||||||
|
|
||||||
let rect = Rect::from_min_size(
|
|
||||||
output.response.rect.right_top() - vec2(output.response.rect.height(), 0.0),
|
|
||||||
vec2(output.response.rect.height(), output.response.rect.height()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// search clear button
|
|
||||||
if ui
|
|
||||||
.put(
|
|
||||||
rect,
|
|
||||||
NavItem::new("\u{2715}", field.is_empty())
|
|
||||||
.color(ui.visuals().widgets.inactive.fg_stroke.color)
|
|
||||||
.active_color(ui.visuals().widgets.active.fg_stroke.color)
|
|
||||||
.hover_color(ui.visuals().hyperlink_color)
|
|
||||||
.sense(Sense::click()),
|
|
||||||
)
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
field.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_important_button_visuals(ui: &mut Ui, app: &GossipUi) {
|
pub(super) fn set_important_button_visuals(ui: &mut Ui, app: &GossipUi) {
|
||||||
let visuals = ui.visuals_mut();
|
let visuals = ui.visuals_mut();
|
||||||
visuals.widgets.inactive.weak_bg_fill = app.theme.accent_color();
|
visuals.widgets.inactive.weak_bg_fill = app.theme.accent_color();
|
||||||
|
@ -36,7 +36,7 @@ impl MoreMenu {
|
|||||||
above_or_below: None,
|
above_or_below: None,
|
||||||
hover_text: None,
|
hover_text: None,
|
||||||
accent_color: app.theme.accent_color(),
|
accent_color: app.theme.accent_color(),
|
||||||
options_symbol: app.options_symbol.clone(),
|
options_symbol: app.assets.options_symbol.clone(),
|
||||||
style: MoreMenuStyle::Simple,
|
style: MoreMenuStyle::Simple,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ impl MoreMenu {
|
|||||||
above_or_below: None,
|
above_or_below: None,
|
||||||
hover_text: None,
|
hover_text: None,
|
||||||
accent_color: app.theme.accent_color(),
|
accent_color: app.theme.accent_color(),
|
||||||
options_symbol: app.options_symbol.clone(),
|
options_symbol: app.assets.options_symbol.clone(),
|
||||||
style: MoreMenuStyle::Bubble,
|
style: MoreMenuStyle::Bubble,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ impl RelayEntry {
|
|||||||
accent_hover,
|
accent_hover,
|
||||||
bg_fill: app.theme.main_content_bgcolor(),
|
bg_fill: app.theme.main_content_bgcolor(),
|
||||||
// highlight: None,
|
// highlight: None,
|
||||||
option_symbol: (&app.options_symbol).into(),
|
option_symbol: (&app.assets.options_symbol).into(),
|
||||||
auth_require_permission: false,
|
auth_require_permission: false,
|
||||||
conn_require_permission: false,
|
conn_require_permission: false,
|
||||||
}
|
}
|
||||||
|
@ -1,72 +1,100 @@
|
|||||||
use egui_winit::egui::{self, vec2, Color32, Id, Rect, Response, Stroke, Ui, Vec2, Widget};
|
use std::{ops::Sub, sync::Arc};
|
||||||
|
|
||||||
|
use egui_winit::egui::{
|
||||||
|
self, vec2, Color32, Galley, Id, Rect, Response, Stroke, TextStyle, Ui, Vec2, Widget,
|
||||||
|
WidgetText,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::ui::Theme;
|
use crate::ui::Theme;
|
||||||
|
|
||||||
|
use super::WidgetState;
|
||||||
|
|
||||||
pub struct Switch<'a> {
|
pub struct Switch<'a> {
|
||||||
value: &'a mut bool,
|
value: &'a mut bool,
|
||||||
|
text: Option<WidgetText>,
|
||||||
size: Vec2,
|
size: Vec2,
|
||||||
knob_fill: Option<Color32>,
|
theme: &'a Theme,
|
||||||
on_fill: Option<Color32>,
|
|
||||||
off_fill: Option<Color32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Switch<'a> {
|
impl<'a> Switch<'a> {
|
||||||
#[allow(unused)]
|
/// Create a small switch, similar to normal line height
|
||||||
pub fn onoff(theme: &Theme, value: &'a mut bool) -> Self {
|
pub fn small(theme: &'a Theme, value: &'a mut bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value,
|
value,
|
||||||
size: theme.get_style().spacing.interact_size.y * vec2(1.6, 0.8),
|
text: None,
|
||||||
knob_fill: Some(theme.get_style().visuals.extreme_bg_color),
|
size: vec2(29.0, 16.0),
|
||||||
on_fill: Some(theme.accent_color()),
|
theme,
|
||||||
off_fill: Some(theme.get_style().visuals.widgets.inactive.bg_fill),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
/// Create a large switch
|
||||||
pub fn toggle(theme: &Theme, value: &'a mut bool) -> Self {
|
pub fn large(theme: &'a Theme, value: &'a mut bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value,
|
value,
|
||||||
size: theme.get_style().spacing.interact_size.y * vec2(1.6, 0.8),
|
text: None,
|
||||||
knob_fill: None,
|
size: vec2(40.0, 22.0),
|
||||||
on_fill: None,
|
theme,
|
||||||
off_fill: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a label that will be displayed to the right of the switch
|
||||||
|
pub fn with_label(mut self, text: impl Into<WidgetText>) -> Self {
|
||||||
|
self.text = Some(text.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(mut self, ui: &mut Ui) -> Response {
|
||||||
|
let (response, galley) = self.allocate(ui);
|
||||||
|
let (state, response) = interact(ui, response, self.value);
|
||||||
|
draw_at(
|
||||||
|
ui, self.theme, self.value, response, self.size, galley, state,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn show_at(mut self, ui: &mut Ui, id: Id, rect: Rect) -> Response {
|
||||||
|
// let response = self.interact_at(ui, id, rect);
|
||||||
|
// let (state, response) = interact(ui, response, self.value);
|
||||||
|
// draw_at(ui, self.value, response, state, self.theme)
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn allocate(&mut self, ui: &mut Ui) -> (Response, Option<Arc<Galley>>) {
|
||||||
|
let (extra_width, galley) = if let Some(text) = self.text.take() {
|
||||||
|
let available_width = ui.available_width() - self.size.y - ui.spacing().item_spacing.y;
|
||||||
|
let galley = text.into_galley(ui, Some(false), available_width, TextStyle::Body);
|
||||||
|
(
|
||||||
|
galley.rect.width() + ui.spacing().item_spacing.y,
|
||||||
|
Some(galley),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(0.0, None)
|
||||||
|
};
|
||||||
|
let sense = if ui.is_enabled() {
|
||||||
|
egui::Sense::click()
|
||||||
|
} else {
|
||||||
|
egui::Sense::hover()
|
||||||
|
};
|
||||||
|
// allocate
|
||||||
|
let (_, response) = ui.allocate_exact_size(self.size + vec2(extra_width, 0.0), sense);
|
||||||
|
(response, galley)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn interact_at(&mut self, ui: &mut Ui, id: Id, rect: Rect) -> Response {
|
||||||
|
// let sense = if ui.is_enabled() {
|
||||||
|
// egui::Sense::click()
|
||||||
|
// } else {
|
||||||
|
// egui::Sense::hover()
|
||||||
|
// };
|
||||||
|
// // just interact
|
||||||
|
// ui.interact(rect, id, sense)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Switch<'a> {
|
impl<'a> Widget for Switch<'a> {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let (rect, _) = ui.allocate_exact_size(self.size, egui::Sense::hover());
|
self.show(ui)
|
||||||
let id = ui.auto_id_with("sw");
|
|
||||||
switch_custom_at(
|
|
||||||
ui,
|
|
||||||
ui.is_enabled(),
|
|
||||||
self.value,
|
|
||||||
rect,
|
|
||||||
id,
|
|
||||||
self.knob_fill,
|
|
||||||
self.on_fill,
|
|
||||||
self.off_fill,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn switch_with_size(ui: &mut Ui, on: &mut bool, size: egui::Vec2) -> Response {
|
|
||||||
let (rect, _) = ui.allocate_exact_size(size, egui::Sense::click());
|
|
||||||
switch_with_size_at(ui, on, size, rect.left_top(), ui.auto_id_with("sw"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn switch_with_size_at(
|
|
||||||
ui: &mut Ui,
|
|
||||||
value: &mut bool,
|
|
||||||
size: egui::Vec2,
|
|
||||||
pos: egui::Pos2,
|
|
||||||
id: Id,
|
|
||||||
) -> Response {
|
|
||||||
let rect = Rect::from_min_size(pos, size);
|
|
||||||
switch_custom_at(ui, ui.is_enabled(), value, rect, id, None, None, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn switch_custom_at(
|
pub fn switch_custom_at(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
@ -134,3 +162,169 @@ pub fn switch_custom_at(
|
|||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn interact(ui: &Ui, response: Response, value: &mut bool) -> (WidgetState, Response) {
|
||||||
|
let (state, mut response) = if response.is_pointer_button_down_on() {
|
||||||
|
(WidgetState::Active, response)
|
||||||
|
} else if response.has_focus() {
|
||||||
|
(WidgetState::Focused, response)
|
||||||
|
} else if response.hovered() || response.highlighted() {
|
||||||
|
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
|
||||||
|
(WidgetState::Hovered, response)
|
||||||
|
} else if !ui.is_enabled() {
|
||||||
|
(WidgetState::Disabled, response)
|
||||||
|
} else {
|
||||||
|
(WidgetState::Default, response)
|
||||||
|
};
|
||||||
|
|
||||||
|
if response.clicked() {
|
||||||
|
*value = !*value;
|
||||||
|
response.mark_changed();
|
||||||
|
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *value, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
(state, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_at(
|
||||||
|
ui: &mut Ui,
|
||||||
|
theme: &Theme,
|
||||||
|
value: &bool,
|
||||||
|
response: Response,
|
||||||
|
size: Vec2,
|
||||||
|
galley: Option<Arc<Galley>>,
|
||||||
|
_state: WidgetState,
|
||||||
|
) -> Response {
|
||||||
|
let rect = response.rect;
|
||||||
|
if ui.is_rect_visible(rect) {
|
||||||
|
let how_on = ui.ctx().animate_bool(response.id, *value);
|
||||||
|
|
||||||
|
let radius = 0.5 * rect.height();
|
||||||
|
let stroke_width = 0.5;
|
||||||
|
let (bg_fill, frame_stroke, knob_fill, knob_stroke, text_color) = if theme.dark_mode {
|
||||||
|
if ui.is_enabled() {
|
||||||
|
if *value {
|
||||||
|
(
|
||||||
|
// on
|
||||||
|
theme.accent_dark(),
|
||||||
|
Stroke::new(stroke_width, theme.accent_dark()),
|
||||||
|
theme.neutral_50(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_300()),
|
||||||
|
theme.neutral_50(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
// off
|
||||||
|
theme.neutral_800(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_600()),
|
||||||
|
theme.neutral_100(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_700()),
|
||||||
|
theme.neutral_50(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
// disabled
|
||||||
|
theme.neutral_800(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_600()),
|
||||||
|
theme.neutral_700(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_600()),
|
||||||
|
theme.neutral_400(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ui.is_enabled() {
|
||||||
|
if *value {
|
||||||
|
(
|
||||||
|
// on
|
||||||
|
theme.accent_light(),
|
||||||
|
Stroke::new(stroke_width, theme.accent_light()),
|
||||||
|
theme.neutral_50(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_300()),
|
||||||
|
theme.neutral_900(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
// off
|
||||||
|
theme.neutral_200(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_400()),
|
||||||
|
theme.neutral_50(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_300()),
|
||||||
|
theme.neutral_900(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
// disabled
|
||||||
|
theme.neutral_300(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_400()),
|
||||||
|
theme.neutral_400(),
|
||||||
|
Stroke::new(stroke_width, theme.neutral_300()),
|
||||||
|
theme.neutral_400(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// switch
|
||||||
|
let switch_rect = Rect::from_min_size(rect.min, size);
|
||||||
|
ui.painter()
|
||||||
|
.rect(switch_rect, radius, bg_fill, frame_stroke);
|
||||||
|
let circle_x = egui::lerp(
|
||||||
|
(switch_rect.left() + radius)..=(switch_rect.right() - radius),
|
||||||
|
how_on,
|
||||||
|
);
|
||||||
|
let center = egui::pos2(circle_x, switch_rect.center().y);
|
||||||
|
ui.painter()
|
||||||
|
.circle(center, radius.sub(1.0), knob_fill, knob_stroke);
|
||||||
|
|
||||||
|
// label
|
||||||
|
if let Some(galley) = galley {
|
||||||
|
let text_pos = switch_rect.right_top()
|
||||||
|
+ vec2(
|
||||||
|
ui.spacing().item_spacing.x,
|
||||||
|
(switch_rect.height() - galley.rect.height()) / 2.0,
|
||||||
|
);
|
||||||
|
ui.painter()
|
||||||
|
.galley_with_override_text_color(text_pos, galley, text_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.has_focus() {
|
||||||
|
// focus ring
|
||||||
|
// https://www.researchgate.net/publication/265893293_Approximation_of_a_cubic_bezier_curve_by_circular_arcs_and_vice_versa
|
||||||
|
// figure 4, formula 7
|
||||||
|
const K: f32 = 0.551_915_05; // 0.5519150244935105707435627;
|
||||||
|
const PHI: f32 = std::f32::consts::PI / 4.0; // 1/8 of circle
|
||||||
|
const GROW: f32 = 3.0; // amount to increase radius over switch rounding5
|
||||||
|
let mut rect = switch_rect.expand(GROW);
|
||||||
|
let rad = rect.height() / 2.0;
|
||||||
|
rect.set_width(rect.height());
|
||||||
|
let center = rect.center();
|
||||||
|
let p1 = Vec2 {
|
||||||
|
x: -rad * f32::cos(PHI),
|
||||||
|
y: rad * f32::sin(PHI),
|
||||||
|
};
|
||||||
|
let p4 = Vec2 {
|
||||||
|
x: -rad * f32::cos(PHI),
|
||||||
|
y: -rad * f32::sin(PHI),
|
||||||
|
};
|
||||||
|
let p2 = Vec2 {
|
||||||
|
x: p1.x - K * rad * f32::sin(PHI),
|
||||||
|
y: p1.y - K * rad * f32::cos(PHI),
|
||||||
|
};
|
||||||
|
let p3 = Vec2 {
|
||||||
|
x: p4.x - K * rad * f32::sin(PHI),
|
||||||
|
y: p4.y + K * rad * f32::cos(PHI),
|
||||||
|
};
|
||||||
|
let points = [center + p1, center + p2, center + p3, center + p4];
|
||||||
|
let ring = egui::epaint::CubicBezierShape::from_points_stroke(
|
||||||
|
points,
|
||||||
|
false,
|
||||||
|
Color32::TRANSPARENT,
|
||||||
|
egui::Stroke::new(1.0, frame_stroke.color),
|
||||||
|
);
|
||||||
|
ui.painter().add(ring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
use egui_winit::egui::{self, vec2, Color32, Rect, TextBuffer, Widget, WidgetText};
|
use egui_winit::egui::{
|
||||||
|
self, load::SizedTexture, vec2, Color32, Rect, Rounding, Sense, Stroke, TextBuffer,
|
||||||
|
TextureHandle, Widget, WidgetText,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::ui::{
|
||||||
|
assets::{self, Assets},
|
||||||
|
Theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::NavItem;
|
||||||
|
|
||||||
pub struct TextEdit<'t> {
|
pub struct TextEdit<'t> {
|
||||||
|
theme: &'t Theme,
|
||||||
text: &'t mut dyn TextBuffer,
|
text: &'t mut dyn TextBuffer,
|
||||||
multiline: bool,
|
multiline: bool,
|
||||||
desired_width: Option<f32>,
|
desired_width: Option<f32>,
|
||||||
@ -10,11 +21,21 @@ pub struct TextEdit<'t> {
|
|||||||
text_color: Option<Color32>,
|
text_color: Option<Color32>,
|
||||||
with_paste: bool,
|
with_paste: bool,
|
||||||
with_clear: bool,
|
with_clear: bool,
|
||||||
|
with_search: bool,
|
||||||
|
magnifyingglass_symbol: Option<TextureHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MARGIN: egui::Margin = egui::Margin {
|
||||||
|
left: 8.0,
|
||||||
|
right: 8.0,
|
||||||
|
top: 4.5,
|
||||||
|
bottom: 4.5,
|
||||||
|
};
|
||||||
|
|
||||||
impl<'t> TextEdit<'t> {
|
impl<'t> TextEdit<'t> {
|
||||||
pub fn singleline(text: &'t mut dyn TextBuffer) -> Self {
|
pub fn singleline(theme: &'t Theme, text: &'t mut dyn TextBuffer) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
theme,
|
||||||
text,
|
text,
|
||||||
multiline: false,
|
multiline: false,
|
||||||
desired_width: None,
|
desired_width: None,
|
||||||
@ -24,6 +45,25 @@ impl<'t> TextEdit<'t> {
|
|||||||
text_color: None,
|
text_color: None,
|
||||||
with_paste: false,
|
with_paste: false,
|
||||||
with_clear: false,
|
with_clear: false,
|
||||||
|
with_search: false,
|
||||||
|
magnifyingglass_symbol: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search(theme: &'t Theme, assets: &Assets, text: &'t mut dyn TextBuffer) -> Self {
|
||||||
|
Self {
|
||||||
|
theme,
|
||||||
|
text,
|
||||||
|
multiline: false,
|
||||||
|
desired_width: None,
|
||||||
|
hint_text: WidgetText::default(),
|
||||||
|
password: false,
|
||||||
|
bg_color: None,
|
||||||
|
text_color: None,
|
||||||
|
with_paste: false,
|
||||||
|
with_clear: true,
|
||||||
|
with_search: true,
|
||||||
|
magnifyingglass_symbol: Some(assets.magnifyingglass_symbol.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,19 +125,26 @@ impl<'t> TextEdit<'t> {
|
|||||||
|
|
||||||
pub fn show(self, ui: &mut egui::Ui) -> egui::text_edit::TextEditOutput {
|
pub fn show(self, ui: &mut egui::Ui) -> egui::text_edit::TextEditOutput {
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
if ui.visuals().dark_mode {
|
self.set_visuals(ui);
|
||||||
ui.visuals_mut().extreme_bg_color =
|
|
||||||
self.bg_color.unwrap_or(egui::Color32::from_gray(0x47));
|
let pre_space = if self.with_search { 20.0 } else { 0.0 };
|
||||||
} else {
|
let margin = egui::Margin {
|
||||||
ui.visuals_mut().extreme_bg_color = self.bg_color.unwrap_or(Color32::WHITE);
|
left: MARGIN.left + pre_space,
|
||||||
}
|
right: MARGIN.right,
|
||||||
|
top: MARGIN.top,
|
||||||
|
bottom: MARGIN.bottom,
|
||||||
|
};
|
||||||
|
|
||||||
|
let where_to_put_background = ui.painter().add(egui::Shape::Noop);
|
||||||
|
|
||||||
let mut inner = match self.multiline {
|
let mut inner = match self.multiline {
|
||||||
false => egui::widgets::TextEdit::singleline(self.text),
|
false => egui::widgets::TextEdit::singleline(self.text),
|
||||||
true => egui::widgets::TextEdit::multiline(self.text),
|
true => egui::widgets::TextEdit::multiline(self.text),
|
||||||
}
|
}
|
||||||
|
.frame(false)
|
||||||
.password(self.password)
|
.password(self.password)
|
||||||
.hint_text(self.hint_text.clone());
|
.hint_text(self.hint_text.clone())
|
||||||
|
.margin(margin); // set margin
|
||||||
|
|
||||||
if let Some(width) = self.desired_width {
|
if let Some(width) = self.desired_width {
|
||||||
inner = inner.desired_width(width);
|
inner = inner.desired_width(width);
|
||||||
@ -107,9 +154,88 @@ impl<'t> TextEdit<'t> {
|
|||||||
inner = inner.text_color(color);
|
inner = inner.text_color(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// show inner
|
// ---- show inner ----
|
||||||
let output = inner.show(ui);
|
let output = inner.show(ui);
|
||||||
|
|
||||||
|
// ---- draw frame ----
|
||||||
|
{
|
||||||
|
let theme = self.theme;
|
||||||
|
let response = &output.response;
|
||||||
|
let frame_rect = response.rect;
|
||||||
|
|
||||||
|
// this is how egui chooses the visual style:
|
||||||
|
#[allow(clippy::if_same_then_else)]
|
||||||
|
let (bg_color, frame_stroke) = if ui.visuals().dark_mode {
|
||||||
|
if !response.sense.interactive() {
|
||||||
|
(theme.neutral_800(), Stroke::new(1.0, theme.neutral_400()))
|
||||||
|
} else if response.is_pointer_button_down_on() || response.has_focus() {
|
||||||
|
(theme.neutral_800(), Stroke::new(1.0, theme.neutral_300()))
|
||||||
|
} else if response.hovered() || response.highlighted() {
|
||||||
|
(theme.neutral_800(), Stroke::new(1.0, theme.neutral_400()))
|
||||||
|
} else {
|
||||||
|
(theme.neutral_800(), Stroke::new(1.0, theme.neutral_400()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !response.sense.interactive() {
|
||||||
|
(theme.neutral_50(), Stroke::new(1.0, theme.neutral_400()))
|
||||||
|
} else if response.is_pointer_button_down_on() || response.has_focus() {
|
||||||
|
(theme.neutral_50(), Stroke::new(1.0, theme.neutral_500()))
|
||||||
|
} else if response.hovered() || response.highlighted() {
|
||||||
|
(theme.neutral_50(), Stroke::new(1.0, theme.neutral_400()))
|
||||||
|
} else {
|
||||||
|
(theme.neutral_50(), Stroke::new(1.0, theme.neutral_400()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rounding = Rounding::same(6.0);
|
||||||
|
|
||||||
|
let shape =
|
||||||
|
egui::epaint::RectShape::new(frame_rect, rounding, bg_color, frame_stroke);
|
||||||
|
|
||||||
|
ui.painter().set(where_to_put_background, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- draw decorations ----
|
||||||
|
if self.with_search {
|
||||||
|
if let Some(symbol) = self.magnifyingglass_symbol {
|
||||||
|
let rect = Rect::from_center_size(
|
||||||
|
output.response.rect.left_center()
|
||||||
|
+ vec2((MARGIN.left + pre_space) / 2.0, 0.0),
|
||||||
|
symbol.size_vec2() / (assets::SVG_OVERSAMPLE + ui.ctx().zoom_factor()),
|
||||||
|
);
|
||||||
|
egui::Image::from_texture(SizedTexture::new(symbol.id(), symbol.size_vec2()))
|
||||||
|
.fit_to_exact_size(rect.size())
|
||||||
|
.tint(if self.theme.dark_mode {
|
||||||
|
self.theme.neutral_500()
|
||||||
|
} else {
|
||||||
|
self.theme.neutral_400()
|
||||||
|
})
|
||||||
|
.paint_at(ui, rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.with_clear && !self.text.as_str().is_empty() {
|
||||||
|
let rect = Rect::from_min_size(
|
||||||
|
output.response.rect.right_top() - vec2(output.response.rect.height(), 0.0),
|
||||||
|
vec2(output.response.rect.height(), output.response.rect.height()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// clear button
|
||||||
|
if ui
|
||||||
|
.put(
|
||||||
|
rect,
|
||||||
|
NavItem::new("\u{2715}", self.text.as_str().is_empty())
|
||||||
|
.color(ui.visuals().widgets.inactive.fg_stroke.color)
|
||||||
|
.active_color(ui.visuals().widgets.active.fg_stroke.color)
|
||||||
|
.hover_color(ui.visuals().hyperlink_color)
|
||||||
|
.sense(Sense::click()),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.text.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// paste button
|
// paste button
|
||||||
if self.with_paste {
|
if self.with_paste {
|
||||||
let action_size = vec2(45.0, output.response.rect.height());
|
let action_size = vec2(45.0, output.response.rect.height());
|
||||||
@ -143,6 +269,43 @@ impl<'t> TextEdit<'t> {
|
|||||||
|
|
||||||
impl<'t> Widget for TextEdit<'t> {
|
impl<'t> Widget for TextEdit<'t> {
|
||||||
fn ui(self, ui: &mut egui_winit::egui::Ui) -> egui_winit::egui::Response {
|
fn ui(self, ui: &mut egui_winit::egui::Ui) -> egui_winit::egui::Response {
|
||||||
self.show(ui).response
|
let output = self.show(ui);
|
||||||
|
output.response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextEdit<'_> {
|
||||||
|
fn set_visuals(&self, ui: &mut egui::Ui) {
|
||||||
|
// this is how egui chooses the visual style:
|
||||||
|
// if !response.sense.interactive() {
|
||||||
|
// &self.noninteractive
|
||||||
|
// } else if response.is_pointer_button_down_on() || response.has_focus() {
|
||||||
|
// &self.active
|
||||||
|
// } else if response.hovered() || response.highlighted() {
|
||||||
|
// &self.hovered
|
||||||
|
// } else {
|
||||||
|
// &self.inactive
|
||||||
|
// }
|
||||||
|
let theme = self.theme;
|
||||||
|
let visuals = ui.visuals_mut();
|
||||||
|
|
||||||
|
// cursor (enabled)
|
||||||
|
visuals.text_cursor = Stroke::new(3.0, theme.accent_color());
|
||||||
|
|
||||||
|
if visuals.dark_mode {
|
||||||
|
// text color (enabled)
|
||||||
|
visuals.widgets.inactive.fg_stroke.color = theme.neutral_50();
|
||||||
|
|
||||||
|
// text selection
|
||||||
|
visuals.selection.bg_fill = theme.accent_color();
|
||||||
|
visuals.selection.stroke = Stroke::new(1.0, Color32::WHITE);
|
||||||
|
} else {
|
||||||
|
// text color (enabled)
|
||||||
|
visuals.widgets.inactive.fg_stroke.color = theme.neutral_800();
|
||||||
|
|
||||||
|
// text selection
|
||||||
|
visuals.selection.bg_fill = theme.accent_color();
|
||||||
|
visuals.selection.stroke = Stroke::new(1.0, Color32::WHITE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user