mirror of
https://github.com/danbulant/cushy
synced 2026-06-19 14:31:04 +00:00
Added Copy to Clipboard to theme editor
This also wires up the beginnings of the application type
This commit is contained in:
parent
0e6796318b
commit
55eea5fad3
7 changed files with 155 additions and 40 deletions
|
|
@ -1,13 +1,13 @@
|
|||
use gooey::value::Dynamic;
|
||||
use gooey::widget::{MakeWidget, WidgetInstance};
|
||||
use gooey::window::ThemeMode;
|
||||
use gooey::Run;
|
||||
use gooey::{Gooey, Run};
|
||||
|
||||
fn main() -> gooey::Result {
|
||||
let theme_mode = Dynamic::default();
|
||||
set_of_containers(3, theme_mode.clone())
|
||||
.centered()
|
||||
.into_window()
|
||||
.into_window(Gooey::default())
|
||||
.themed_mode(theme_mode)
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use gooey::styles::components::{TextColor, WidgetBackground};
|
||||
use gooey::styles::{
|
||||
ColorScheme, ColorSchemeBuilder, ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme,
|
||||
|
|
@ -10,7 +12,7 @@ use gooey::widgets::input::InputValue;
|
|||
use gooey::widgets::slider::Slidable;
|
||||
use gooey::widgets::Space;
|
||||
use gooey::window::ThemeMode;
|
||||
use gooey::Run;
|
||||
use gooey::{Gooey, Run};
|
||||
use kludgine::figures::units::Lp;
|
||||
use kludgine::Color;
|
||||
use palette::OklabHue;
|
||||
|
|
@ -75,6 +77,8 @@ impl<Primary, Other> Scheme<Primary, Other> {
|
|||
}
|
||||
|
||||
fn main() -> gooey::Result {
|
||||
let gooey = Gooey::default();
|
||||
|
||||
let (theme_mode, theme_switcher) = dark_mode_picker();
|
||||
|
||||
let scheme = Scheme::from(ColorScheme::default());
|
||||
|
|
@ -93,7 +97,7 @@ fn main() -> gooey::Result {
|
|||
(opt_color, editor)
|
||||
},
|
||||
);
|
||||
let color_scheme = (
|
||||
let color_scheme_builder = (
|
||||
&sources.primary,
|
||||
&editors.secondary.0,
|
||||
&editors.tertiary.0,
|
||||
|
|
@ -109,9 +113,10 @@ fn main() -> gooey::Result {
|
|||
scheme.error = error;
|
||||
scheme.neutral = neutral;
|
||||
scheme.neutral_variant = neutral_variant;
|
||||
scheme.build()
|
||||
scheme
|
||||
},
|
||||
);
|
||||
let color_scheme = color_scheme_builder.map_each_cloned(|builder| builder.build());
|
||||
color_scheme
|
||||
.for_each_cloned(move |scheme| {
|
||||
sources.primary.set(scheme.primary);
|
||||
|
|
@ -131,6 +136,21 @@ fn main() -> gooey::Result {
|
|||
.and(editors.error.1)
|
||||
.and(editors.neutral.1)
|
||||
.and(editors.neutral_variant.1)
|
||||
.and("Copy to Clipboard".into_button().on_click({
|
||||
let gooey = gooey.clone();
|
||||
move |()| {
|
||||
if let Some(mut clipboard) = gooey.clipboard_guard() {
|
||||
let builder = color_scheme_builder.get();
|
||||
let mut source = String::default();
|
||||
builder.format_rust_into(&mut source);
|
||||
|
||||
if let Err(err) = clipboard.set_text(&source) {
|
||||
tracing::error!("Error setting clipboard text: {err}");
|
||||
println!("{source}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
.into_rows()
|
||||
.vertical_scroll();
|
||||
|
||||
|
|
@ -152,7 +172,7 @@ fn main() -> gooey::Result {
|
|||
.themed(theme)
|
||||
.pad()
|
||||
.expand()
|
||||
.into_window()
|
||||
.into_window(gooey)
|
||||
.themed_mode(theme_mode)
|
||||
.run()
|
||||
}
|
||||
|
|
@ -416,3 +436,54 @@ fn swatch(background: Dynamic<Color>, label: &str, text: Dynamic<Color>) -> impl
|
|||
.fit_vertically()
|
||||
.expand()
|
||||
}
|
||||
|
||||
trait FormatRust {
|
||||
fn format_rust_into(&self, out: &mut String);
|
||||
}
|
||||
|
||||
impl FormatRust for ColorSource {
|
||||
fn format_rust_into(&self, out: &mut String) {
|
||||
write!(
|
||||
out,
|
||||
"ColorSource::new({:.1}, {:.1})",
|
||||
self.hue.into_degrees(),
|
||||
self.saturation
|
||||
)
|
||||
.expect("writing to string")
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatRust for ColorSchemeBuilder {
|
||||
fn format_rust_into(&self, source: &mut String) {
|
||||
if self.secondary.is_none()
|
||||
&& self.tertiary.is_none()
|
||||
&& self.error.is_none()
|
||||
&& self.neutral.is_none()
|
||||
&& self.neutral_variant.is_none()
|
||||
{
|
||||
source.push_str("ColorScheme::from_primary(");
|
||||
self.primary.format_rust_into(source);
|
||||
source.push(')');
|
||||
} else {
|
||||
source.push_str("ColorSchemeBuilder::new(");
|
||||
self.primary.format_rust_into(source);
|
||||
source.push_str(").");
|
||||
for (label, color) in [
|
||||
self.secondary.map(|secondary| ("secondary", secondary)),
|
||||
self.tertiary.map(|color| ("tertiary", color)),
|
||||
self.error.map(|color| ("error", color)),
|
||||
self.neutral.map(|color| ("neutral", color)),
|
||||
self.neutral_variant.map(|color| ("neutral_variant", color)),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
source.push_str(label);
|
||||
source.push('(');
|
||||
color.format_rust_into(source);
|
||||
source.push_str(").");
|
||||
}
|
||||
source.push_str("build()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31
src/app.rs
Normal file
31
src/app.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use arboard::Clipboard;
|
||||
|
||||
use crate::utils::IgnorePoison;
|
||||
|
||||
/// A GUI application.
|
||||
#[derive(Clone)]
|
||||
pub struct Gooey {
|
||||
pub(crate) clipboard: Option<Arc<Mutex<Clipboard>>>,
|
||||
}
|
||||
|
||||
impl Default for Gooey {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
clipboard: Clipboard::new()
|
||||
.ok()
|
||||
.map(|clipboard| Arc::new(Mutex::new(clipboard))),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Gooey {
|
||||
/// Returns a locked mutex guard to the OS's clipboard, if one was able to be
|
||||
/// initialized when the window opened.
|
||||
#[must_use]
|
||||
pub fn clipboard_guard(&self) -> Option<MutexGuard<'_, Clipboard>> {
|
||||
self.clipboard
|
||||
.as_ref()
|
||||
.map(|mutex| mutex.lock().ignore_poison())
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ mod graphics;
|
|||
mod names;
|
||||
#[macro_use]
|
||||
pub mod styles;
|
||||
mod app;
|
||||
mod tick;
|
||||
mod tree;
|
||||
pub mod value;
|
||||
|
|
@ -22,6 +23,7 @@ pub mod widgets;
|
|||
pub mod window;
|
||||
use std::ops::Sub;
|
||||
|
||||
pub use app::Gooey;
|
||||
pub use kludgine;
|
||||
use kludgine::app::winit::error::EventLoopError;
|
||||
use kludgine::figures::units::UPx;
|
||||
|
|
|
|||
|
|
@ -2039,7 +2039,7 @@ impl RequireInvalidation for ContainerLevel {
|
|||
}
|
||||
|
||||
/// A builder of [`ColorScheme`]s.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ColorSchemeBuilder {
|
||||
/// The primary color of the scheme.
|
||||
pub primary: ColorSource,
|
||||
|
|
@ -2134,7 +2134,17 @@ impl ColorSchemeBuilder {
|
|||
/// 33% of the primary saturation will be picked.
|
||||
#[must_use]
|
||||
pub fn tertiary(mut self, tertiary: impl ProtoColor) -> Self {
|
||||
self.secondary = Some(tertiary.into_source(self.primary.saturation / 3.));
|
||||
self.tertiary = Some(tertiary.into_source(self.primary.saturation / 3.));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the error color and returns self.
|
||||
///
|
||||
/// If `error` doesn't specify a saturation, the primary color's saturation
|
||||
/// will be used.
|
||||
#[must_use]
|
||||
pub fn error(mut self, error: impl ProtoColor) -> Self {
|
||||
self.error = Some(error.into_source(self.primary.saturation));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use kludgine::figures::units::{Px, UPx};
|
|||
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
|
||||
use kludgine::Color;
|
||||
|
||||
use crate::app::Gooey;
|
||||
use crate::context::sealed::WindowHandle;
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
|
||||
use crate::styles::components::{
|
||||
|
|
@ -680,8 +681,8 @@ pub trait MakeWidget: Sized {
|
|||
fn make_widget(self) -> WidgetInstance;
|
||||
|
||||
/// Returns a new window containing `self` as the root widget.
|
||||
fn into_window(self) -> Window<WidgetInstance> {
|
||||
Window::new(self.make_widget())
|
||||
fn into_window(self, gooey: Gooey) -> Window<WidgetInstance> {
|
||||
Window::new(self.make_widget(), gooey.clone())
|
||||
}
|
||||
|
||||
/// Associates `styles` with this widget.
|
||||
|
|
@ -1289,7 +1290,7 @@ impl WidgetInstance {
|
|||
|
||||
/// Runs this widget instance as an application.
|
||||
pub fn run(self) -> crate::Result {
|
||||
Window::<WidgetInstance>::new(self).run()
|
||||
Window::<WidgetInstance>::new(self, Gooey::default()).run()
|
||||
}
|
||||
|
||||
/// Returns the id of the widget that should receive focus after this
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::ops::{Deref, DerefMut, Not};
|
|||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
||||
use std::path::Path;
|
||||
use std::string::ToString;
|
||||
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
|
||||
use std::sync::{MutexGuard, OnceLock};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use alot::LotId;
|
||||
|
|
@ -27,6 +27,7 @@ use kludgine::Kludgine;
|
|||
use tracing::Level;
|
||||
|
||||
use crate::animation::{LinearInterpolate, PercentBetween, ZeroToOne};
|
||||
use crate::app::Gooey;
|
||||
use crate::context::{
|
||||
AsEventContext, EventContext, Exclusive, GraphicsContext, InvalidationStatus, LayoutContext,
|
||||
WidgetContext,
|
||||
|
|
@ -34,7 +35,7 @@ use crate::context::{
|
|||
use crate::graphics::Graphics;
|
||||
use crate::styles::{Edges, FontFamilyList, ThemePair};
|
||||
use crate::tree::Tree;
|
||||
use crate::utils::{IgnorePoison, ModifiersExt};
|
||||
use crate::utils::ModifiersExt;
|
||||
use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value};
|
||||
use crate::widget::{
|
||||
EventHandling, ManagedWidget, RootBehavior, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED,
|
||||
|
|
@ -45,7 +46,7 @@ use crate::{initialize_tracing, ConstraintLimit, Run};
|
|||
/// A currently running Gooey window.
|
||||
pub struct RunningWindow<'window> {
|
||||
window: kludgine::app::Window<'window, WindowCommand>,
|
||||
clipboard: Option<Arc<Mutex<Clipboard>>>,
|
||||
gooey: Gooey,
|
||||
focused: Dynamic<bool>,
|
||||
occluded: Dynamic<bool>,
|
||||
}
|
||||
|
|
@ -53,13 +54,13 @@ pub struct RunningWindow<'window> {
|
|||
impl<'window> RunningWindow<'window> {
|
||||
pub(crate) fn new(
|
||||
window: kludgine::app::Window<'window, WindowCommand>,
|
||||
clipboard: &Option<Arc<Mutex<Clipboard>>>,
|
||||
gooey: &Gooey,
|
||||
focused: &Dynamic<bool>,
|
||||
occluded: &Dynamic<bool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
clipboard: clipboard.clone(),
|
||||
gooey: gooey.clone(),
|
||||
focused: focused.clone(),
|
||||
occluded: occluded.clone(),
|
||||
}
|
||||
|
|
@ -82,10 +83,8 @@ impl<'window> RunningWindow<'window> {
|
|||
/// Returns a locked mutex guard to the OS's clipboard, if one was able to be
|
||||
/// initialized when the window opened.
|
||||
#[must_use]
|
||||
pub fn clipboard_guard(&mut self) -> Option<MutexGuard<'_, Clipboard>> {
|
||||
self.clipboard
|
||||
.as_ref()
|
||||
.map(|mutex| mutex.lock().ignore_poison())
|
||||
pub fn clipboard_guard(&self) -> Option<MutexGuard<'_, Clipboard>> {
|
||||
self.gooey.clipboard_guard()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +112,7 @@ where
|
|||
Behavior: WindowBehavior,
|
||||
{
|
||||
context: Behavior::Context,
|
||||
gooey: Gooey,
|
||||
/// The attributes of this window.
|
||||
pub attributes: WindowAttributes,
|
||||
/// The colors to use to theme the user interface.
|
||||
|
|
@ -148,7 +148,7 @@ where
|
|||
{
|
||||
fn default() -> Self {
|
||||
let context = Behavior::Context::default();
|
||||
Self::new(context)
|
||||
Self::new(context, Gooey::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ impl Window<WidgetInstance> {
|
|||
where
|
||||
W: Widget,
|
||||
{
|
||||
Self::new(WidgetInstance::new(widget))
|
||||
Self::new(WidgetInstance::new(widget), Gooey::default())
|
||||
}
|
||||
|
||||
/// Sets `focused` to be the dynamic updated when this window's focus status
|
||||
|
|
@ -222,7 +222,7 @@ where
|
|||
{
|
||||
/// Returns a new instance using `context` to initialize the window upon
|
||||
/// opening.
|
||||
pub fn new(context: Behavior::Context) -> Self {
|
||||
pub fn new(context: Behavior::Context, gooey: Gooey) -> Self {
|
||||
static EXECUTABLE_NAME: OnceLock<String> = OnceLock::new();
|
||||
|
||||
let title = EXECUTABLE_NAME
|
||||
|
|
@ -243,6 +243,7 @@ where
|
|||
title,
|
||||
..WindowAttributes::default()
|
||||
},
|
||||
gooey,
|
||||
context,
|
||||
load_system_fonts: true,
|
||||
theme: Value::default(),
|
||||
|
|
@ -267,6 +268,7 @@ where
|
|||
GooeyWindow::<Behavior>::run_with(AssertUnwindSafe(sealed::Context {
|
||||
user: self.context,
|
||||
settings: RefCell::new(sealed::WindowSettings {
|
||||
gooey: self.gooey,
|
||||
transparent: self.attributes.transparent,
|
||||
attributes: Some(self.attributes),
|
||||
occluded: self.occluded,
|
||||
|
|
@ -313,7 +315,7 @@ pub trait WindowBehavior: Sized + 'static {
|
|||
|
||||
/// Runs this behavior as an application, initialized with `context`.
|
||||
fn run_with(context: Self::Context) -> crate::Result {
|
||||
Window::<Self>::new(context).run()
|
||||
Window::<Self>::new(context, Gooey::default()).run()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,7 +337,7 @@ struct GooeyWindow<T> {
|
|||
current_theme: ThemePair,
|
||||
theme_mode: Value<ThemeMode>,
|
||||
transparent: bool,
|
||||
clipboard: Option<Arc<Mutex<Clipboard>>>,
|
||||
gooey: Gooey,
|
||||
}
|
||||
|
||||
impl<T> GooeyWindow<T>
|
||||
|
|
@ -526,6 +528,7 @@ where
|
|||
AssertUnwindSafe(context): Self::Context,
|
||||
) -> Self {
|
||||
let mut settings = context.settings.borrow_mut();
|
||||
let gooey = settings.gooey.clone();
|
||||
let occluded = settings.occluded.take().unwrap_or_default();
|
||||
let focused = settings.focused.take().unwrap_or_default();
|
||||
let theme = settings.theme.take().expect("theme always present");
|
||||
|
|
@ -563,10 +566,6 @@ where
|
|||
fontdb.set_cursive_family(name);
|
||||
}
|
||||
|
||||
let clipboard = Clipboard::new()
|
||||
.ok()
|
||||
.map(|clipboard| Arc::new(Mutex::new(clipboard)));
|
||||
|
||||
let theme_mode = match settings.theme_mode.take() {
|
||||
Some(Value::Dynamic(dynamic)) => {
|
||||
dynamic.set(window.theme().into());
|
||||
|
|
@ -577,7 +576,7 @@ where
|
|||
};
|
||||
let transparent = settings.transparent;
|
||||
let mut behavior = T::initialize(
|
||||
&mut RunningWindow::new(window, &clipboard, &focused, &occluded),
|
||||
&mut RunningWindow::new(window, &gooey, &focused, &occluded),
|
||||
context.user,
|
||||
);
|
||||
let root = Tree::default().push_boxed(behavior.make_root(), None);
|
||||
|
|
@ -608,7 +607,7 @@ where
|
|||
theme,
|
||||
theme_mode,
|
||||
transparent,
|
||||
clipboard,
|
||||
gooey,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -633,7 +632,7 @@ where
|
|||
.new_frame(self.redraw_status.invalidations().drain());
|
||||
|
||||
let resizable = window.winit().is_resizable();
|
||||
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
|
||||
let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded);
|
||||
let root_mode = self.constrain_window_resizing(resizable, &mut window, graphics);
|
||||
|
||||
let graphics = self.contents.new_frame(graphics);
|
||||
|
|
@ -754,7 +753,7 @@ where
|
|||
Self::request_close(
|
||||
&mut self.should_close,
|
||||
&mut self.behavior,
|
||||
&mut RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded),
|
||||
&mut RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -818,7 +817,7 @@ where
|
|||
let Some(target) = self.root.tree.widget_from_node(target) else {
|
||||
return;
|
||||
};
|
||||
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
|
||||
let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded);
|
||||
let mut target = EventContext::new(
|
||||
WidgetContext::new(
|
||||
target,
|
||||
|
|
@ -928,7 +927,7 @@ where
|
|||
.expect("missing widget")
|
||||
});
|
||||
|
||||
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
|
||||
let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded);
|
||||
let mut widget = EventContext::new(
|
||||
WidgetContext::new(
|
||||
widget,
|
||||
|
|
@ -964,7 +963,7 @@ where
|
|||
.widget(self.root.id())
|
||||
.expect("missing widget")
|
||||
});
|
||||
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
|
||||
let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded);
|
||||
let mut target = EventContext::new(
|
||||
WidgetContext::new(
|
||||
widget,
|
||||
|
|
@ -991,7 +990,7 @@ where
|
|||
let location = Point::<Px>::from(position);
|
||||
self.cursor.location = Some(location);
|
||||
|
||||
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
|
||||
let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded);
|
||||
|
||||
EventContext::new(
|
||||
WidgetContext::new(
|
||||
|
|
@ -1038,8 +1037,7 @@ where
|
|||
_device_id: DeviceId,
|
||||
) {
|
||||
if self.cursor.widget.take().is_some() {
|
||||
let mut window =
|
||||
RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
|
||||
let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded);
|
||||
let mut context = EventContext::new(
|
||||
WidgetContext::new(
|
||||
self.root.clone(),
|
||||
|
|
@ -1063,7 +1061,7 @@ where
|
|||
state: ElementState,
|
||||
button: MouseButton,
|
||||
) {
|
||||
let mut window = RunningWindow::new(window, &self.clipboard, &self.focused, &self.occluded);
|
||||
let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded);
|
||||
match state {
|
||||
ElementState::Pressed => {
|
||||
EventContext::new(
|
||||
|
|
@ -1198,6 +1196,7 @@ pub(crate) mod sealed {
|
|||
use crate::styles::{FontFamilyList, ThemePair};
|
||||
use crate::value::{Dynamic, Value};
|
||||
use crate::window::{ThemeMode, WindowAttributes};
|
||||
use crate::Gooey;
|
||||
|
||||
pub struct Context<C> {
|
||||
pub user: C,
|
||||
|
|
@ -1205,6 +1204,7 @@ pub(crate) mod sealed {
|
|||
}
|
||||
|
||||
pub struct WindowSettings {
|
||||
pub gooey: Gooey,
|
||||
pub attributes: Option<WindowAttributes>,
|
||||
pub occluded: Option<Dynamic<bool>>,
|
||||
pub focused: Option<Dynamic<bool>>,
|
||||
|
|
|
|||
Loading…
Reference in a new issue