mirror of https://github.com/terhechte/postsack
Many small improvements to the look
Also added a platform module for specific looks depending on the platform.pull/1/head
parent
a606538524
commit
25b6ddda99
@ -0,0 +1,21 @@
|
|||||||
|
#![cfg(target_os = "linux")]
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
use super::PlatformColors;
|
||||||
|
|
||||||
|
pub fn platform_colors() -> PlatformColors {
|
||||||
|
// From Google images, Gtk
|
||||||
|
PlatformColors {
|
||||||
|
window_background_dark: Color32::from_rgb(53, 53, 53),
|
||||||
|
window_background_light: Color32::from_rgb(246, 245, 244),
|
||||||
|
content_background_dark: Color32::from_rgb(34, 32, 40),
|
||||||
|
content_background_light: Color32::from_rgb(254, 254, 254),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is called from `App::setup`
|
||||||
|
pub fn setup(ctx: &egui::CtxRef) {}
|
||||||
|
|
||||||
|
/// This is called once from `App::update` on the first run.
|
||||||
|
pub fn initial_update(ctx: &egui::CtxRef) -> Result<()> {}
|
@ -0,0 +1,34 @@
|
|||||||
|
#![cfg(target_os = "macos")]
|
||||||
|
|
||||||
|
use cocoa;
|
||||||
|
use eframe::egui::{self, Color32};
|
||||||
|
use eyre::{bail, Result};
|
||||||
|
use objc::runtime::{Object, YES};
|
||||||
|
|
||||||
|
use super::PlatformColors;
|
||||||
|
|
||||||
|
pub fn platform_colors() -> PlatformColors {
|
||||||
|
PlatformColors {
|
||||||
|
window_background_dark: Color32::from_rgb(36, 30, 42),
|
||||||
|
window_background_light: Color32::from_rgb(238, 236, 242),
|
||||||
|
content_background_dark: Color32::from_rgb(20, 14, 26),
|
||||||
|
content_background_light: Color32::from_rgb(236, 234, 238),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is called from `App::setup`
|
||||||
|
pub fn setup(_ctx: &egui::CtxRef) {}
|
||||||
|
|
||||||
|
/// This is called once from `App::update` on the first run.
|
||||||
|
pub fn initial_update(_ctx: &egui::CtxRef) -> Result<()> {
|
||||||
|
unsafe {
|
||||||
|
let app = cocoa::appkit::NSApp();
|
||||||
|
if app.is_null() {
|
||||||
|
bail!("Could not retrieve NSApp");
|
||||||
|
}
|
||||||
|
let main_window: *mut Object = msg_send![app, mainWindow];
|
||||||
|
|
||||||
|
let _: () = msg_send![main_window, setTitlebarAppearsTransparent: YES];
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
mod windows;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub use windows::{initial_update, platform_colors, setup};
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod linux;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub use linux::{initial_update, platform_colors, setup};
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
mod macos;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub use macos::{initial_update, platform_colors, setup};
|
||||||
|
|
||||||
|
use eframe::egui::Color32;
|
||||||
|
/// Platform-Native Colors
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PlatformColors {
|
||||||
|
pub window_background_dark: Color32,
|
||||||
|
pub window_background_light: Color32,
|
||||||
|
pub content_background_dark: Color32,
|
||||||
|
pub content_background_light: Color32,
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
#![cfg(target_os = "windows")]
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
use super::PlatformColors;
|
||||||
|
|
||||||
|
pub fn platform_colors() -> PlatformColors {
|
||||||
|
// From Google images, Windows 11
|
||||||
|
PlatformColors {
|
||||||
|
window_background_dark: Color32::from_rgb(32, 32, 32),
|
||||||
|
window_background_light: Color32::from_rgb(241, 243, 246),
|
||||||
|
content_background_dark: Color32::from_rgb(34, 32, 40),
|
||||||
|
content_background_light: Color32::from_rgb(251, 251, 253),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is called from `App::setup`
|
||||||
|
pub fn setup(ctx: &egui::CtxRef) {}
|
||||||
|
|
||||||
|
/// This is called once from `App::update` on the first run.
|
||||||
|
pub fn initial_update(ctx: &egui::CtxRef) -> Result<()> {}
|
@ -0,0 +1,52 @@
|
|||||||
|
use crate::model::{segmentations, Engine};
|
||||||
|
use eframe::egui::{self, Widget};
|
||||||
|
use eyre::Report;
|
||||||
|
|
||||||
|
pub struct SegmentationBar<'a> {
|
||||||
|
engine: &'a mut Engine,
|
||||||
|
error: &'a mut Option<Report>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SegmentationBar<'a> {
|
||||||
|
pub fn new(engine: &'a mut Engine, error: &'a mut Option<Report>) -> Self {
|
||||||
|
Self { engine, error }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for SegmentationBar<'a> {
|
||||||
|
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.set_height(30.0);
|
||||||
|
|
||||||
|
let groupings = segmentations::aggregated_by(self.engine);
|
||||||
|
let has_back = groupings.len() > 1;
|
||||||
|
for (id_index, group) in groupings.iter().enumerate() {
|
||||||
|
let alternatives = segmentations::aggregation_fields(self.engine, group);
|
||||||
|
if let Some(value) = group.value() {
|
||||||
|
let label = egui::Label::new(format!("{}: {}", group.name(), value));
|
||||||
|
ui.add(label);
|
||||||
|
} else if let Some(mut selected) = group.index(&alternatives) {
|
||||||
|
let p = egui::ComboBox::from_id_source(&id_index).show_index(
|
||||||
|
ui,
|
||||||
|
&mut selected,
|
||||||
|
alternatives.len(),
|
||||||
|
|i| alternatives[i].as_str().to_string(),
|
||||||
|
);
|
||||||
|
if p.changed() {
|
||||||
|
*self.error = segmentations::set_aggregation(
|
||||||
|
self.engine,
|
||||||
|
group,
|
||||||
|
&alternatives[selected],
|
||||||
|
)
|
||||||
|
.err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_back && ui.button("Back").clicked() {
|
||||||
|
self.engine.pop();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.response
|
||||||
|
}
|
||||||
|
}
|
@ -1,50 +1,80 @@
|
|||||||
use crate::model::{segmentations, Engine};
|
use crate::model::Engine;
|
||||||
use eframe::egui::{self, Widget};
|
use eframe::egui::{self, Widget};
|
||||||
use eyre::Report;
|
use eyre::Report;
|
||||||
|
|
||||||
|
use super::app::UIState;
|
||||||
|
use super::widgets::FilterPanel;
|
||||||
|
|
||||||
pub struct TopBar<'a> {
|
pub struct TopBar<'a> {
|
||||||
engine: &'a mut Engine,
|
engine: &'a mut Engine,
|
||||||
|
#[allow(unused)]
|
||||||
error: &'a mut Option<Report>,
|
error: &'a mut Option<Report>,
|
||||||
|
state: &'a mut UIState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TopBar<'a> {
|
impl<'a> TopBar<'a> {
|
||||||
pub fn new(engine: &'a mut Engine, error: &'a mut Option<Report>) -> Self {
|
pub fn new(
|
||||||
TopBar { engine, error }
|
engine: &'a mut Engine,
|
||||||
|
error: &'a mut Option<Report>,
|
||||||
|
state: &'a mut UIState,
|
||||||
|
) -> Self {
|
||||||
|
TopBar {
|
||||||
|
engine,
|
||||||
|
error,
|
||||||
|
state,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for TopBar<'a> {
|
impl<'a> Widget for TopBar<'a> {
|
||||||
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
ui.horizontal(|ui| {
|
let response = ui
|
||||||
let groupings = segmentations::aggregated_by(self.engine);
|
.horizontal(|ui| {
|
||||||
let has_back = groupings.len() > 1;
|
ui.set_height(40.0);
|
||||||
for (id_index, group) in groupings.iter().enumerate() {
|
|
||||||
let alternatives = segmentations::aggregation_fields(self.engine, group);
|
ui.add_space(15.0);
|
||||||
if let Some(value) = group.value() {
|
|
||||||
let label = egui::Label::new(format!("{}: {}", group.name(), value));
|
if ui.add(egui::Button::new("Close")).clicked() {
|
||||||
ui.add(label);
|
self.state.action_close = true;
|
||||||
} else if let Some(mut selected) = group.index(&alternatives) {
|
}
|
||||||
let p = egui::ComboBox::from_id_source(&id_index).show_index(
|
|
||||||
ui,
|
let filter_response = ui.add(egui::Button::new("Filters"));
|
||||||
&mut selected,
|
let popup_id = ui.make_persistent_id("my_unique_id");
|
||||||
alternatives.len(),
|
|
||||||
|i| alternatives[i].as_str().to_string(),
|
if filter_response.clicked() {
|
||||||
);
|
ui.memory().toggle_popup(popup_id);
|
||||||
if p.changed() {
|
}
|
||||||
*self.error = segmentations::set_aggregation(
|
|
||||||
self.engine,
|
egui::popup_below_widget(ui, popup_id, &filter_response, |ui| {
|
||||||
group,
|
ui.add(FilterPanel::new(self.engine));
|
||||||
&alternatives[selected],
|
});
|
||||||
)
|
|
||||||
.err();
|
// This is a hack to get right-alignment.
|
||||||
}
|
// we can't size the button, we can only size text. We will size text
|
||||||
}
|
// and then use ~that for these buttons
|
||||||
}
|
let mut w = ui.available_width();
|
||||||
|
|
||||||
if has_back && ui.button("Back").clicked() {
|
let mail_text = "Mails";
|
||||||
self.engine.pop();
|
let mail_galley = ui
|
||||||
|
.painter()
|
||||||
|
.layout_no_wrap(egui::TextStyle::Button, mail_text.to_owned());
|
||||||
|
|
||||||
|
let filter_text = "Export";
|
||||||
|
let filter_galley = ui
|
||||||
|
.painter()
|
||||||
|
.layout_no_wrap(egui::TextStyle::Button, filter_text.to_owned());
|
||||||
|
|
||||||
|
w -= mail_galley.size.x + ui.spacing().button_padding.x * 4.0;
|
||||||
|
w -= filter_galley.size.x + ui.spacing().button_padding.x * 4.0;
|
||||||
|
ui.add_space(w);
|
||||||
|
|
||||||
|
ui.add(egui::Button::new(filter_text));
|
||||||
|
|
||||||
|
if ui.add(egui::Button::new(mail_text)).clicked() {
|
||||||
|
self.state.show_emails = !self.state.show_emails;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.response
|
.response;
|
||||||
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/// This will draw Ui with a background color and margins.
|
||||||
|
/// This can be used for calls that don't provide a `Frame`,
|
||||||
|
/// such as `horizontal` or `vertical`
|
||||||
|
use eframe::egui::{self, Color32, Rect, Stroke, Ui};
|
||||||
|
|
||||||
|
pub fn background_color<R>(
|
||||||
|
ui: &mut Ui,
|
||||||
|
padding: f32,
|
||||||
|
stroke: Stroke,
|
||||||
|
fill: Color32,
|
||||||
|
show: impl FnOnce(&mut Ui) -> R,
|
||||||
|
) -> R {
|
||||||
|
let outer_rect_bounds = ui.available_rect_before_wrap();
|
||||||
|
let where_to_put_background = ui.painter().add(egui::Shape::Noop);
|
||||||
|
let margin = egui::Vec2::splat(padding);
|
||||||
|
let inner_rect = outer_rect_bounds.shrink2(margin);
|
||||||
|
|
||||||
|
let mut content_ui = ui.child_ui(inner_rect, *ui.layout());
|
||||||
|
let ret = show(&mut content_ui);
|
||||||
|
let outer_rect = Rect::from_min_max(outer_rect_bounds.min, content_ui.min_rect().max + margin);
|
||||||
|
let (rect, _) = ui.allocate_at_least(outer_rect.size(), egui::Sense::hover());
|
||||||
|
|
||||||
|
ui.painter().set(
|
||||||
|
where_to_put_background,
|
||||||
|
egui::epaint::Shape::Rect {
|
||||||
|
corner_radius: 0.0,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
rect,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ret
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
//! A panel to edit filters
|
||||||
|
use eframe::egui::{self, vec2, Response, TextStyle, Vec2, Widget};
|
||||||
|
|
||||||
|
use crate::model::{segmentations, Engine};
|
||||||
|
|
||||||
|
pub struct FilterPanel<'a> {
|
||||||
|
engine: &'a mut Engine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FilterPanel<'a> {
|
||||||
|
pub fn default_size() -> Vec2 {
|
||||||
|
vec2(200.0, 400.0)
|
||||||
|
}
|
||||||
|
pub fn new(engine: &'a mut Engine) -> Self {
|
||||||
|
Self { engine }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for FilterPanel<'a> {
|
||||||
|
fn ui(self, ui: &mut egui::Ui) -> Response {
|
||||||
|
let Self { engine } = self;
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
if let Some((range, total)) = segmentations::segments_range(engine) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Limit");
|
||||||
|
let mut selected = total;
|
||||||
|
let response = ui.add(egui::Slider::new(&mut selected, range));
|
||||||
|
if response.changed() {
|
||||||
|
segmentations::set_segments_range(engine, Some(0..=selected));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ui.label("label");
|
||||||
|
ui.label("label");
|
||||||
|
ui.label("label");
|
||||||
|
ui.label("label");
|
||||||
|
ui.label("label");
|
||||||
|
ui.label("label");
|
||||||
|
ui.label("label");
|
||||||
|
})
|
||||||
|
.response
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,12 @@
|
|||||||
|
pub mod background;
|
||||||
mod error_box;
|
mod error_box;
|
||||||
|
mod filter_panel;
|
||||||
mod rectangles;
|
mod rectangles;
|
||||||
mod spinner;
|
mod spinner;
|
||||||
mod table;
|
mod table;
|
||||||
|
|
||||||
pub use error_box::ErrorBox;
|
pub use error_box::ErrorBox;
|
||||||
|
pub use filter_panel::FilterPanel;
|
||||||
pub use rectangles::Rectangles;
|
pub use rectangles::Rectangles;
|
||||||
pub use spinner::Spinner;
|
pub use spinner::Spinner;
|
||||||
pub use table::Table;
|
pub use table::Table;
|
||||||
|
Loading…
Reference in New Issue