1
0
Fork 0

Center the text entry field

This commit is contained in:
Robbert van der Helm 2022-03-15 13:06:05 +01:00
parent 61c60b5c73
commit 6144fc0011

View file

@ -2,14 +2,15 @@
use atomic_refcell::AtomicRefCell; use atomic_refcell::AtomicRefCell;
use nih_plug::prelude::{GuiContext, Param, ParamSetter}; use nih_plug::prelude::{GuiContext, Param, ParamSetter};
use std::borrow::Borrow;
use crate::backend::widget; use crate::backend::widget;
use crate::backend::Renderer; use crate::backend::Renderer;
use crate::renderer::Renderer as GraphicsRenderer; use crate::renderer::Renderer as GraphicsRenderer;
use crate::text::Renderer as TextRenderer; use crate::text::Renderer as TextRenderer;
use crate::{ use crate::{
alignment, event, keyboard, layout, mouse, renderer, text, touch, Clipboard, Color, Element, alignment, event, keyboard, layout, mouse, renderer, text, touch, Background, Clipboard, Color,
Event, Font, Layout, Length, Point, Rectangle, Shell, Size, TextInput, Vector, Widget, Element, Event, Font, Layout, Length, Point, Rectangle, Shell, Size, TextInput, Vector, Widget,
}; };
use super::util; use super::util;
@ -75,11 +76,16 @@ struct TextInputStyle;
impl widget::text_input::StyleSheet for TextInputStyle { impl widget::text_input::StyleSheet for TextInputStyle {
fn active(&self) -> widget::text_input::Style { fn active(&self) -> widget::text_input::Style {
widget::text_input::Style::default() widget::text_input::Style {
background: Background::Color(Color::TRANSPARENT),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
} }
fn focused(&self) -> widget::text_input::Style { fn focused(&self) -> widget::text_input::Style {
widget::text_input::Style::default() self.active()
} }
fn placeholder_color(&self) -> Color { fn placeholder_color(&self) -> Color {
@ -175,16 +181,21 @@ impl<'a, P: Param> Widget<ParamMessage, Renderer> for ParamSlider<'a, P> {
let event = event.clone(); let event = event.clone();
let mut messages = Vec::new(); let mut messages = Vec::new();
let mut shell = Shell::new(&mut messages); let mut shell = Shell::new(&mut messages);
let status = self.with_text_input(layout, current_value, |mut text_input, layout| { let status = self.with_text_input(
text_input.on_event( layout,
event, renderer,
layout, current_value,
cursor_position, |mut text_input, layout, renderer| {
renderer, text_input.on_event(
clipboard, event,
&mut shell, layout,
) cursor_position,
}); renderer,
clipboard,
&mut shell,
)
},
);
// Pressing escape will unfocus the text field, so we should propagate that change in // Pressing escape will unfocus the text field, so we should propagate that change in
// our own model // our own model
@ -384,39 +395,45 @@ impl<'a, P: Param> Widget<ParamMessage, Renderer> for ParamSlider<'a, P> {
background_color, background_color,
); );
// We'll visualize the difference between the current value and the default value
let current_value = self.param.normalized_value();
let fill_start_x = util::remap_rect_x_t(
&bounds,
self.setter.default_normalized_param_value(self.param),
);
let fill_end_x = util::remap_rect_x_t(&bounds, current_value);
let fill_color = Color::from_rgb8(196, 196, 196);
let fill_rect = Rectangle {
x: fill_start_x.min(fill_end_x),
y: bounds.y + BORDER_WIDTH,
width: (fill_end_x - fill_start_x).abs(),
height: bounds.height - BORDER_WIDTH * 2.0,
};
renderer.fill_quad(
renderer::Quad {
bounds: fill_rect,
border_color: Color::TRANSPARENT,
border_width: 0.0,
border_radius: 0.0,
},
fill_color,
);
// Only draw the text input widget when it gets focussed. Otherwise, overlay the label with // Only draw the text input widget when it gets focussed. Otherwise, overlay the label with
// the slider. To make it more readable (and because it looks cool), the parts that overlap // the slider.
// with the fill rect will be rendered in white while the rest will be rendered in black.
if let Some(current_value) = &self.state.text_input_value { if let Some(current_value) = &self.state.text_input_value {
self.with_text_input(layout, current_value, |text_input, layout| { self.with_text_input(
text_input.draw(renderer, layout, cursor_position, None) layout,
}) renderer,
current_value,
|text_input, layout, renderer| {
text_input.draw(renderer, layout, cursor_position, None)
},
)
} else { } else {
// We'll visualize the difference between the current value and the default value
let current_value = self.param.normalized_value();
let fill_start_x = util::remap_rect_x_t(
&bounds,
self.setter.default_normalized_param_value(self.param),
);
let fill_end_x = util::remap_rect_x_t(&bounds, current_value);
let fill_color = Color::from_rgb8(196, 196, 196);
let fill_rect = Rectangle {
x: fill_start_x.min(fill_end_x),
y: bounds.y + BORDER_WIDTH,
width: (fill_end_x - fill_start_x).abs(),
height: bounds.height - BORDER_WIDTH * 2.0,
};
renderer.fill_quad(
renderer::Quad {
bounds: fill_rect,
border_color: Color::TRANSPARENT,
border_width: 0.0,
border_radius: 0.0,
},
fill_color,
);
// To make it more readable (and because it looks cool), the parts that overlap with the
// fill rect will be rendered in white while the rest will be rendered in black.
let display_value = self.param.to_string(); let display_value = self.param.to_string();
let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()) as f32; let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()) as f32;
let text_bounds = Rectangle { let text_bounds = Rectangle {
@ -466,42 +483,49 @@ impl<'a, P: Param> ParamSlider<'a, P> {
/// Create a temporary [`TextInput`] hooked up to [`State::text_input_value`] and outputting /// Create a temporary [`TextInput`] hooked up to [`State::text_input_value`] and outputting
/// [`TextInputMessage`] messages and do something with it. This can be used to /// [`TextInputMessage`] messages and do something with it. This can be used to
fn with_text_input<R, F: FnOnce(TextInput<'_, TextInputMessage>, Layout) -> R>( fn with_text_input<T, R, F>(&self, layout: Layout, renderer: R, current_value: &str, f: F) -> T
&self, where
layout: Layout, F: FnOnce(TextInput<'_, TextInputMessage>, Layout, R) -> T,
current_value: &str, R: Borrow<Renderer>,
f: F, {
) -> R {
let mut text_input_state = self.state.text_input_state.borrow_mut(); let mut text_input_state = self.state.text_input_state.borrow_mut();
text_input_state.focus(); text_input_state.focus();
let text_size = self
.text_size
.unwrap_or_else(|| renderer.borrow().default_size());
let text_width = renderer
.borrow()
.measure_width(current_value, text_size, self.font);
let text_input = TextInput::new( let text_input = TextInput::new(
&mut text_input_state, &mut text_input_state,
"", "",
current_value, current_value,
TextInputMessage::Value, TextInputMessage::Value,
) )
.width(self.width) .font(self.font)
.size(text_size)
.width(Length::Units(text_width.ceil() as u16))
.style(TextInputStyle) .style(TextInputStyle)
.on_submit(TextInputMessage::Submit); .on_submit(TextInputMessage::Submit);
// Make sure to not draw over the borders // Make sure to not draw over the borders, and center the text
let offset_node = layout::Node::with_children( let offset_node = layout::Node::with_children(
Size { Size {
width: layout.bounds().size().width - (BORDER_WIDTH * 2.0), width: text_width,
height: layout.bounds().size().height - (BORDER_WIDTH * 2.0), height: layout.bounds().size().height - (BORDER_WIDTH * 2.0),
}, },
vec![layout::Node::new(layout.bounds().size())], vec![layout::Node::new(layout.bounds().size())],
); );
let offset_layout = Layout::with_offset( let offset_layout = Layout::with_offset(
Vector { Vector {
x: layout.position().x + BORDER_WIDTH, x: layout.bounds().center_x() - (text_width / 2.0),
y: layout.position().y + BORDER_WIDTH, y: layout.position().y + BORDER_WIDTH,
}, },
&offset_node, &offset_node,
); );
f(text_input, offset_layout) f(text_input, offset_layout, renderer)
} }
/// Set the normalized value for a parameter if that would change the parameter's plain value /// Set the normalized value for a parameter if that would change the parameter's plain value