[color] add HSV-based color picker for struct Srgba
This commit is contained in:
parent
fc3582fbe1
commit
d8e0b3bff6
15 changed files with 451 additions and 23 deletions
7
TODO.md
7
TODO.md
|
@ -10,9 +10,12 @@ TODO-list for the Egui project. If you looking for something to do, look here.
|
|||
* [ ] Clipboard copy/paste
|
||||
* [ ] Move focus with tab
|
||||
* [ ] Horizontal slider
|
||||
* [ ] Color picker
|
||||
* [/] Color picker
|
||||
* [x] linear rgb <-> sRGB
|
||||
* Containers:
|
||||
* [x] HSV
|
||||
* [x] Color edit button with popup color picker
|
||||
* [ ] Easily edit users own (s)RGBA quadruplets (`&mut [u8;4]`/`[f32;4]`)
|
||||
* Containers
|
||||
* [ ] Scroll areas
|
||||
* [x] Vertical scrolling
|
||||
* [x] Scroll-wheel input
|
||||
|
|
47
egui/src/cache.rs
Normal file
47
egui/src/cache.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use std::hash::{Hash, Hasher};
|
||||
|
||||
const SIZE: usize = 8 * 1024;
|
||||
|
||||
/// Very stupid/simple key-value cache. TODO: improve
|
||||
#[derive(Clone)]
|
||||
pub struct Cache<K, V>([Option<(K, V)>; SIZE]);
|
||||
|
||||
impl<K, V> Default for Cache<K, V>
|
||||
where
|
||||
K: Copy,
|
||||
V: Copy,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self([None; SIZE])
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> std::fmt::Debug for Cache<K, V> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Cache")
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Cache<K, V>
|
||||
where
|
||||
K: Hash + PartialEq,
|
||||
{
|
||||
pub fn get(&self, key: &K) -> Option<&V> {
|
||||
let bucket = (hash(key) % (SIZE as u64)) as usize;
|
||||
match &self.0[bucket] {
|
||||
Some((k, v)) if k == key => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, key: K, value: V) {
|
||||
let bucket = (hash(&key) % (SIZE as u64)) as usize;
|
||||
self.0[bucket] = Some((key, value));
|
||||
}
|
||||
}
|
||||
|
||||
fn hash(value: impl Hash) -> u64 {
|
||||
let mut hasher = ahash::AHasher::default();
|
||||
value.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
|
@ -12,8 +12,15 @@ pub fn show_tooltip(ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Ui)) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Show a tooltip at the current mouse position (if any).
|
||||
pub fn show_tooltip_text(ctx: &Arc<Context>, text: impl Into<String>) {
|
||||
show_tooltip(ctx, |ui| {
|
||||
ui.add(crate::widgets::Label::new(text));
|
||||
})
|
||||
}
|
||||
|
||||
/// Show a pop-over window.
|
||||
pub fn show_popup(
|
||||
fn show_popup(
|
||||
ctx: &Arc<Context>,
|
||||
id: Id,
|
||||
window_pos: Pos2,
|
||||
|
@ -21,7 +28,7 @@ pub fn show_popup(
|
|||
) -> Response {
|
||||
use containers::*;
|
||||
Area::new(id)
|
||||
.order(Order::Foreground)
|
||||
.order(Order::Tooltip)
|
||||
.fixed_pos(window_pos)
|
||||
.interactable(false)
|
||||
.show(ctx, |ui| Frame::popup(&ctx.style()).show(ui, add_contents))
|
||||
|
|
|
@ -452,6 +452,7 @@ struct Widgets {
|
|||
radio: usize,
|
||||
slider_value: f32,
|
||||
angle: f32,
|
||||
color: Srgba,
|
||||
single_line_text_input: String,
|
||||
multiline_text_input: String,
|
||||
}
|
||||
|
@ -464,6 +465,7 @@ impl Default for Widgets {
|
|||
count: 0,
|
||||
slider_value: 3.4,
|
||||
angle: TAU / 8.0,
|
||||
color: (Rgba::new(0.0, 1.0, 0.5, 1.0) * 0.75).into(),
|
||||
single_line_text_input: "Hello World!".to_owned(),
|
||||
multiline_text_input: "Text can both be so wide that it needs a line break, but you can also add manual line break by pressing enter, creating new paragraphs.\nThis is the start of the next paragraph.\n\nClick me to edit me!".to_owned(),
|
||||
}
|
||||
|
@ -527,6 +529,13 @@ impl Widgets {
|
|||
}
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.add(Label::new("Click to select a different text color: ").text_color(self.color));
|
||||
ui.color_edit_button(&mut self.color);
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(label!("Single line text input:"));
|
||||
ui.add(
|
||||
|
|
|
@ -12,6 +12,8 @@ pub enum Order {
|
|||
Middle,
|
||||
/// Popups, menus etc that should always be painted on top of windows
|
||||
Foreground,
|
||||
/// Foreground objects can also have tooltips
|
||||
Tooltip,
|
||||
/// Debug layer, always painted last / on top
|
||||
Debug,
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
|
||||
mod animation_manager;
|
||||
pub mod app;
|
||||
pub(crate) mod cache;
|
||||
pub mod containers;
|
||||
mod context;
|
||||
pub mod demos;
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::{
|
||||
area, collapsing_header, menu, resize, scroll_area, widgets::text_edit, window, Id, Layer,
|
||||
Pos2, Rect,
|
||||
area,
|
||||
cache::Cache,
|
||||
collapsing_header, menu,
|
||||
paint::color::{Hsva, Srgba},
|
||||
resize, scroll_area,
|
||||
widgets::text_edit,
|
||||
window, Id, Layer, Pos2, Rect,
|
||||
};
|
||||
|
||||
/// The data that Egui persists between frames.
|
||||
|
@ -34,6 +39,14 @@ pub struct Memory {
|
|||
pub(crate) temp_edit_string: Option<String>,
|
||||
|
||||
pub(crate) areas: Areas,
|
||||
|
||||
/// Used by color picker
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub(crate) color_cache: Cache<Srgba, Hsva>,
|
||||
|
||||
/// Which popup-window is open (if any)?
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub(crate) popup: Option<Id>,
|
||||
}
|
||||
|
||||
/// Say there is a button in a scroll area.
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
use crate::math::clamp;
|
||||
|
||||
/// This format is used for space-efficient color representation.
|
||||
///
|
||||
/// Instead of manipulating this directly it is often better
|
||||
/// to first convert it to either `Rgba` or `Hsva`.
|
||||
///
|
||||
/// 0-255 gamma space `sRGBA` color with premultiplied alpha.
|
||||
/// Alpha channel is in linear space.
|
||||
/// This format is used for space-efficient color representation.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Srgba(pub [u8; 4]);
|
||||
|
||||
|
@ -21,10 +25,14 @@ impl std::ops::IndexMut<usize> for Srgba {
|
|||
}
|
||||
|
||||
pub const fn srgba(r: u8, g: u8, b: u8, a: u8) -> Srgba {
|
||||
Srgba([r, g, b, a])
|
||||
Srgba::new(r, g, b, a)
|
||||
}
|
||||
|
||||
impl Srgba {
|
||||
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self([r, g, b, a])
|
||||
}
|
||||
|
||||
pub const fn gray(l: u8) -> Self {
|
||||
Self([l, l, l, 255])
|
||||
}
|
||||
|
@ -36,6 +44,11 @@ impl Srgba {
|
|||
pub const fn additive_luminance(l: u8) -> Self {
|
||||
Self([l, l, l, 0])
|
||||
}
|
||||
|
||||
/// Returns an opaque version of self
|
||||
pub fn to_opaque(self) -> Self {
|
||||
Rgba::from(self).to_opaque().into()
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -93,6 +106,12 @@ impl Rgba {
|
|||
Self([l * a, l * a, l * a, a])
|
||||
}
|
||||
|
||||
/// Transparent black
|
||||
pub fn black_alpha(a: f32) -> Self {
|
||||
debug_assert!(0.0 <= a && a <= 1.0);
|
||||
Self([0.0, 0.0, 0.0, a])
|
||||
}
|
||||
|
||||
/// Transparent white
|
||||
pub fn white_alpha(a: f32) -> Self {
|
||||
debug_assert!(0.0 <= a && a <= 1.0);
|
||||
|
@ -108,6 +127,40 @@ impl Rgba {
|
|||
alpha * self[3],
|
||||
])
|
||||
}
|
||||
|
||||
pub fn r(&self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
pub fn g(&self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
pub fn b(&self) -> f32 {
|
||||
self.0[2]
|
||||
}
|
||||
pub fn a(&self) -> f32 {
|
||||
self.0[3]
|
||||
}
|
||||
|
||||
/// How perceptually intense (bright) is the color?
|
||||
pub fn intensity(&self) -> f32 {
|
||||
0.3 * self.r() + 0.59 * self.g() + 0.11 * self.b()
|
||||
}
|
||||
|
||||
/// Returns an opaque version of self
|
||||
pub fn to_opaque(&self) -> Self {
|
||||
if self.a() == 0.0 {
|
||||
// additive or fully transparent
|
||||
Self::new(self.r(), self.g(), self.b(), 1.0)
|
||||
} else {
|
||||
// un-multiply alpha
|
||||
Self::new(
|
||||
self.r() / self.a(),
|
||||
self.g() / self.a(),
|
||||
self.b() / self.a(),
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Rgba {
|
||||
|
|
|
@ -117,6 +117,22 @@ impl Triangles {
|
|||
self.vertices.push(bottom_right);
|
||||
}
|
||||
|
||||
/// Uniformly colored rectangle.
|
||||
pub fn add_colored_rect(&mut self, rect: Rect, color: Srgba) {
|
||||
self.add_rect(
|
||||
Vertex {
|
||||
pos: rect.min,
|
||||
uv: WHITE_UV,
|
||||
color,
|
||||
},
|
||||
Vertex {
|
||||
pos: rect.max,
|
||||
uv: WHITE_UV,
|
||||
color,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// This is for platforms that only support 16-bit index buffers.
|
||||
///
|
||||
/// Splits this mesh into many smaller meshes (if needed).
|
||||
|
|
|
@ -435,7 +435,7 @@ impl Stroke {
|
|||
ui.label(format!("{}: ", text));
|
||||
ui.add(DragValue::f32(width).speed(0.1).range(0.0..=5.0))
|
||||
.tooltip_text("Width");
|
||||
ui_color(ui, color, "Color");
|
||||
ui.color_edit_button(color);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -449,13 +449,9 @@ fn ui_slider_vec2(ui: &mut Ui, value: &mut Vec2, range: std::ops::RangeInclusive
|
|||
});
|
||||
}
|
||||
|
||||
// TODO: improve color picker
|
||||
fn ui_color(ui: &mut Ui, srgba: &mut Srgba, text: &str) {
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.label(format!("{} sRGBA: ", text));
|
||||
ui.add(DragValue::u8(&mut srgba[0])).tooltip_text("r");
|
||||
ui.add(DragValue::u8(&mut srgba[1])).tooltip_text("g");
|
||||
ui.add(DragValue::u8(&mut srgba[2])).tooltip_text("b");
|
||||
ui.add(DragValue::u8(&mut srgba[3])).tooltip_text("a");
|
||||
ui.label(format!("{}: ", text));
|
||||
ui.color_edit_button(srgba);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -80,9 +80,22 @@ pub struct Response {
|
|||
pub has_kb_focus: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Response {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Response")
|
||||
.field("rect", &self.rect)
|
||||
.field("sense", &self.sense)
|
||||
.field("hovered", &self.hovered)
|
||||
.field("clicked", &self.clicked)
|
||||
.field("double_clicked", &self.double_clicked)
|
||||
.field("active", &self.active)
|
||||
.field("has_kb_focus", &self.has_kb_focus)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Show some stuff if the item was hovered
|
||||
pub fn tooltip(&mut self, add_contents: impl FnOnce(&mut Ui)) -> &mut Self {
|
||||
pub fn tooltip(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
|
||||
if self.hovered {
|
||||
crate::containers::show_tooltip(&self.ctx, add_contents);
|
||||
}
|
||||
|
@ -90,9 +103,9 @@ impl Response {
|
|||
}
|
||||
|
||||
/// Show this text if the item was hovered
|
||||
pub fn tooltip_text(&mut self, text: impl Into<String>) -> &mut Self {
|
||||
self.tooltip(|popup| {
|
||||
popup.add(crate::widgets::Label::new(text));
|
||||
pub fn tooltip_text(self, text: impl Into<String>) -> Self {
|
||||
self.tooltip(|ui| {
|
||||
ui.add(crate::widgets::Label::new(text));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -481,6 +481,12 @@ impl Ui {
|
|||
response
|
||||
}
|
||||
|
||||
/// Shows a button with the given color.
|
||||
/// If the user clicks the button, a full color picker is shown.
|
||||
pub fn color_edit_button(&mut self, srgba: &mut Srgba) {
|
||||
widgets::color_picker::color_edit_button(self, srgba)
|
||||
}
|
||||
|
||||
/// Ask to allocate a certain amount of space and return a Painter for that region.
|
||||
///
|
||||
/// You may get back a `Painter` with a smaller or larger size than what you desired,
|
||||
|
@ -603,6 +609,11 @@ impl Ui {
|
|||
self.inner_layout(Layout::vertical(Align::Min), initial_size, add_contents)
|
||||
}
|
||||
|
||||
pub fn vertical_centered<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
|
||||
let initial_size = vec2(0.0, self.available().height());
|
||||
self.inner_layout(Layout::vertical(Align::Center), initial_size, add_contents)
|
||||
}
|
||||
|
||||
pub fn inner_layout<R>(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
use crate::{layout::Direction, *};
|
||||
|
||||
pub mod color_picker;
|
||||
mod slider;
|
||||
pub(crate) mod text_edit;
|
||||
|
||||
|
|
256
egui/src/widgets/color_picker.rs
Normal file
256
egui/src/widgets/color_picker.rs
Normal file
|
@ -0,0 +1,256 @@
|
|||
use crate::{
|
||||
paint::{color::*, *},
|
||||
*,
|
||||
};
|
||||
|
||||
fn contrast_color(color: impl Into<Rgba>) -> Srgba {
|
||||
if color.into().intensity() < 0.5 {
|
||||
color::WHITE
|
||||
} else {
|
||||
color::BLACK
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of vertices per dimension in the color sliders.
|
||||
/// We need at least 6 for hues, and more for smooth 2D areas.
|
||||
/// Should always be a multiple of 6 to hit the peak hues in HSV/HSL (every 60°).
|
||||
const N: u32 = 6 * 3;
|
||||
|
||||
fn background_checkers(painter: &Painter, rect: Rect) {
|
||||
let mut top_color = Srgba::gray(128);
|
||||
let mut bottom_color = Srgba::gray(32);
|
||||
let checker_size = Vec2::splat(rect.height() / 2.0);
|
||||
let n = (rect.width() / checker_size.x).round() as u32;
|
||||
|
||||
let mut triangles = Triangles::default();
|
||||
for i in 0..n {
|
||||
let x = lerp(rect.left()..=rect.right(), i as f32 / (n as f32));
|
||||
triangles.add_colored_rect(
|
||||
Rect::from_min_size(pos2(x, rect.top()), checker_size),
|
||||
top_color,
|
||||
);
|
||||
triangles.add_colored_rect(
|
||||
Rect::from_min_size(pos2(x, rect.center().y), checker_size),
|
||||
bottom_color,
|
||||
);
|
||||
std::mem::swap(&mut top_color, &mut bottom_color);
|
||||
}
|
||||
painter.add(PaintCmd::Triangles(triangles));
|
||||
}
|
||||
|
||||
fn show_color(ui: &mut Ui, color: Srgba, desired_size: Vec2) -> Rect {
|
||||
let rect = ui.allocate_space(desired_size);
|
||||
background_checkers(ui.painter(), rect);
|
||||
ui.painter().add(PaintCmd::Rect {
|
||||
rect,
|
||||
corner_radius: 2.0,
|
||||
fill: color,
|
||||
stroke: Stroke::new(3.0, color.to_opaque()),
|
||||
});
|
||||
rect
|
||||
}
|
||||
|
||||
fn color_button(ui: &mut Ui, color: Srgba) -> Response {
|
||||
let desired_size = Vec2::splat(ui.style().spacing.clickable_diameter);
|
||||
let rect = ui.allocate_space(desired_size);
|
||||
let rect = rect.expand2(ui.style().spacing.button_expand);
|
||||
let id = ui.make_position_id();
|
||||
let response = ui.interact(rect, id, Sense::click());
|
||||
let visuals = ui.style().interact(&response);
|
||||
background_checkers(ui.painter(), rect);
|
||||
ui.painter().add(PaintCmd::Rect {
|
||||
rect,
|
||||
corner_radius: visuals.corner_radius.min(2.0),
|
||||
fill: color,
|
||||
stroke: visuals.fg_stroke,
|
||||
});
|
||||
response
|
||||
}
|
||||
|
||||
fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Srgba) -> Response {
|
||||
#![allow(clippy::identity_op)]
|
||||
|
||||
let desired_size = vec2(
|
||||
ui.style().spacing.slider_width,
|
||||
ui.style().spacing.clickable_diameter * 2.0,
|
||||
);
|
||||
let rect = ui.allocate_space(desired_size);
|
||||
|
||||
let id = ui.make_position_id();
|
||||
let response = ui.interact(rect, id, Sense::click_and_drag());
|
||||
if response.active {
|
||||
if let Some(mpos) = ui.input().mouse.pos {
|
||||
*value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
|
||||
}
|
||||
}
|
||||
|
||||
let visuals = ui.style().interact(&response);
|
||||
|
||||
background_checkers(ui.painter(), rect); // for alpha:
|
||||
|
||||
{
|
||||
// fill color:
|
||||
let mut triangles = Triangles::default();
|
||||
for i in 0..=N {
|
||||
let t = i as f32 / (N as f32);
|
||||
let color = color_at(t);
|
||||
let x = lerp(rect.left()..=rect.right(), t);
|
||||
triangles.colored_vertex(pos2(x, rect.top()), color);
|
||||
triangles.colored_vertex(pos2(x, rect.bottom()), color);
|
||||
if i < N {
|
||||
triangles.add_triangle(2 * i + 0, 2 * i + 1, 2 * i + 2);
|
||||
triangles.add_triangle(2 * i + 1, 2 * i + 2, 2 * i + 3);
|
||||
}
|
||||
}
|
||||
ui.painter().add(PaintCmd::Triangles(triangles));
|
||||
}
|
||||
|
||||
ui.painter().rect_stroke(rect, 0.0, visuals.bg_stroke); // outline
|
||||
|
||||
{
|
||||
// Show where the slider is at:
|
||||
let x = lerp(rect.left()..=rect.right(), *value);
|
||||
let r = rect.height() / 4.0;
|
||||
let picked_color = color_at(*value);
|
||||
ui.painter().add(PaintCmd::Path {
|
||||
points: vec![
|
||||
pos2(x - r, rect.bottom()),
|
||||
pos2(x + r, rect.bottom()),
|
||||
pos2(x, rect.center().y),
|
||||
],
|
||||
closed: true,
|
||||
fill: picked_color,
|
||||
stroke: Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
|
||||
});
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn color_slider_2d(
|
||||
ui: &mut Ui,
|
||||
x_value: &mut f32,
|
||||
y_value: &mut f32,
|
||||
color_at: impl Fn(f32, f32) -> Srgba,
|
||||
) -> Response {
|
||||
let desired_size = Vec2::splat(ui.style().spacing.slider_width);
|
||||
let rect = ui.allocate_space(desired_size);
|
||||
|
||||
let id = ui.make_position_id();
|
||||
let response = ui.interact(rect, id, Sense::click_and_drag());
|
||||
if response.active {
|
||||
if let Some(mpos) = ui.input().mouse.pos {
|
||||
*x_value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
|
||||
*y_value = remap_clamp(mpos.y, rect.bottom()..=rect.top(), 0.0..=1.0);
|
||||
}
|
||||
}
|
||||
|
||||
let visuals = ui.style().interact(&response);
|
||||
let mut triangles = Triangles::default();
|
||||
|
||||
for xi in 0..=N {
|
||||
for yi in 0..=N {
|
||||
let xt = xi as f32 / (N as f32);
|
||||
let yt = yi as f32 / (N as f32);
|
||||
let color = color_at(xt, yt);
|
||||
let x = lerp(rect.left()..=rect.right(), xt);
|
||||
let y = lerp(rect.bottom()..=rect.top(), yt);
|
||||
triangles.colored_vertex(pos2(x, y), color);
|
||||
|
||||
if xi < N && yi < N {
|
||||
let x_offset = 1;
|
||||
let y_offset = N + 1;
|
||||
let tl = yi * y_offset + xi;
|
||||
triangles.add_triangle(tl, tl + x_offset, tl + y_offset);
|
||||
triangles.add_triangle(tl + x_offset, tl + y_offset, tl + y_offset + x_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.painter().add(PaintCmd::Triangles(triangles)); // fill
|
||||
|
||||
ui.painter().rect_stroke(rect, 0.0, visuals.bg_stroke); // outline
|
||||
|
||||
// Show where the slider is at:
|
||||
let x = lerp(rect.left()..=rect.right(), *x_value);
|
||||
let y = lerp(rect.bottom()..=rect.top(), *y_value);
|
||||
let picked_color = color_at(*x_value, *y_value);
|
||||
ui.painter().add(PaintCmd::Circle {
|
||||
center: pos2(x, y),
|
||||
radius: rect.width() / 12.0,
|
||||
fill: picked_color,
|
||||
stroke: Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
|
||||
});
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva) {
|
||||
ui.vertical_centered(|ui| {
|
||||
let current_color_size = vec2(
|
||||
ui.style().spacing.slider_width,
|
||||
ui.style().spacing.clickable_diameter * 2.0,
|
||||
);
|
||||
let current_color_rect = show_color(ui, (*hsva).into(), current_color_size);
|
||||
if ui.hovered(current_color_rect) {
|
||||
show_tooltip_text(ui.ctx(), "Current color");
|
||||
}
|
||||
|
||||
let opaque = Hsva { a: 1.0, ..*hsva };
|
||||
let Hsva { h, s, v, a } = hsva;
|
||||
color_slider_2d(ui, h, s, |h, s| Hsva::new(h, s, 1.0, 1.0).into())
|
||||
.tooltip_text("Hue - Saturation");
|
||||
color_slider_2d(ui, v, s, |v, s| Hsva { v, s, ..opaque }.into())
|
||||
.tooltip_text("Value - Saturation");
|
||||
ui.label("Alpha:");
|
||||
color_slider_1d(ui, a, |a| Hsva { a, ..opaque }.into()).tooltip_text("Alpha");
|
||||
});
|
||||
}
|
||||
|
||||
fn color_picker_hsva(ui: &mut Ui, hsva: &mut Hsva) {
|
||||
let id = ui.make_position_id().with("foo");
|
||||
let button_response = color_button(ui, (*hsva).into()).tooltip_text("Click to edit color");
|
||||
|
||||
if button_response.clicked {
|
||||
ui.memory().popup = Some(id);
|
||||
}
|
||||
// TODO: make it easier to show a temporary popup that closes when you click outside it
|
||||
if ui.memory().popup == Some(id) {
|
||||
let area_response = Area::new(id)
|
||||
.order(Order::Foreground)
|
||||
.default_pos(button_response.rect.max)
|
||||
.show(ui.ctx(), |ui| {
|
||||
Frame::popup(ui.style()).show(ui, |ui| {
|
||||
color_picker_hsva_2d(ui, hsva);
|
||||
})
|
||||
});
|
||||
|
||||
if !button_response.clicked {
|
||||
let clicked_outside = ui.input().mouse.click && !area_response.hovered;
|
||||
if clicked_outside || ui.input().key_pressed(Key::Escape) {
|
||||
ui.memory().popup = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: return Response so user can show a tooltip
|
||||
/// Shows a button with the given color.
|
||||
/// If the user clicks the button, a full color picker is shown.
|
||||
pub fn color_edit_button(ui: &mut Ui, srgba: &mut Srgba) {
|
||||
// To ensure we keep hue slider when `srgba` is grey we store the
|
||||
// full `Hsva` in a cache:
|
||||
|
||||
let mut hsva = ui
|
||||
.ctx()
|
||||
.memory()
|
||||
.color_cache
|
||||
.get(srgba)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Hsva::from(*srgba));
|
||||
|
||||
color_picker_hsva(ui, &mut hsva);
|
||||
|
||||
*srgba = Srgba::from(hsva);
|
||||
|
||||
ui.ctx().memory().color_cache.set(*srgba, hsva);
|
||||
}
|
|
@ -235,13 +235,13 @@ impl<'a> Slider<'a> {
|
|||
ui.memory().temp_edit_string = Some(value_text);
|
||||
}
|
||||
} else {
|
||||
let mut response = ui.add(
|
||||
let response = ui.add(
|
||||
Label::new(value_text)
|
||||
.multiline(false)
|
||||
.text_color(text_color)
|
||||
.text_style(TextStyle::Monospace),
|
||||
);
|
||||
response.tooltip_text("Click to enter a value");
|
||||
let response = response.tooltip_text("Click to enter a value");
|
||||
let response = ui.interact(response.rect, kb_edit_id, Sense::click());
|
||||
if response.clicked {
|
||||
ui.memory().request_kb_focus(kb_edit_id);
|
||||
|
|
Loading…
Reference in a new issue