feat: better text inputs (#854)
This commit is contained in:
parent
45a970c38a
commit
a8cc4e8b7f
4 changed files with 154 additions and 18 deletions
|
|
@ -1,10 +1,15 @@
|
|||
use freya::prelude::*;
|
||||
|
||||
use crate::components::{Button, Icon, IconType, OverlayPopup};
|
||||
use crate::theme::colors;
|
||||
use crate::ui::clamp_to_window;
|
||||
|
||||
const GAP_VERT: f32 = 8.0;
|
||||
const GAP_HORI: f32 = 12.0;
|
||||
|
||||
const EXPANDED_WIDTH: f32 = 440.;
|
||||
const EXPANDED_HEIGHT: f32 = 180.;
|
||||
|
||||
fn text_input(value: impl Into<Writable<String>>) -> Input {
|
||||
Input::new(value)
|
||||
.inner_margin(Gaps::new_symmetric(GAP_VERT, GAP_HORI))
|
||||
|
|
@ -40,6 +45,10 @@ pub fn validate_memory(validator: InputValidator) {
|
|||
#[derive(Clone, PartialEq)]
|
||||
pub struct TextInput {
|
||||
input: Input,
|
||||
value: Writable<String>,
|
||||
on_submit: Option<EventHandler<String>>,
|
||||
multiline: bool,
|
||||
expandable: bool,
|
||||
layout: LayoutData,
|
||||
style: StyleState,
|
||||
text_style: TextStyleData,
|
||||
|
|
@ -50,12 +59,13 @@ pub struct TextInput {
|
|||
#[allow(dead_code)]
|
||||
impl TextInput {
|
||||
pub fn new(value: impl Into<Writable<String>>) -> Self {
|
||||
Self::from_input(text_input(value))
|
||||
}
|
||||
|
||||
pub fn from_input(input: Input) -> Self {
|
||||
let value = value.into();
|
||||
Self {
|
||||
input,
|
||||
input: text_input(value.clone()),
|
||||
value,
|
||||
on_submit: None,
|
||||
multiline: false,
|
||||
expandable: false,
|
||||
layout: LayoutData::default(),
|
||||
style: StyleState::default(),
|
||||
text_style: TextStyleData::default(),
|
||||
|
|
@ -81,7 +91,20 @@ impl TextInput {
|
|||
}
|
||||
|
||||
pub fn on_submit(mut self, on_submit: impl Into<EventHandler<String>>) -> Self {
|
||||
self.input = self.input.on_submit(on_submit);
|
||||
let on_submit = on_submit.into();
|
||||
self.input = self.input.on_submit(on_submit.clone());
|
||||
self.on_submit = Some(on_submit);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn multiline(mut self, multiline: bool) -> Self {
|
||||
self.input = self.input.multiline(multiline);
|
||||
self.multiline = multiline;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn expandable(mut self, expandable: bool) -> Self {
|
||||
self.expandable = expandable;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -147,18 +170,114 @@ impl StyleExt for TextInput {
|
|||
}
|
||||
}
|
||||
|
||||
fn submit_on_modifier_enter(
|
||||
value: Writable<String>,
|
||||
on_submit: EventHandler<String>,
|
||||
) -> Callback<Event<KeyboardEventData>, bool> {
|
||||
Callback::new(move |e: Event<KeyboardEventData>| match &e.key {
|
||||
Key::Named(NamedKey::Enter) if e.modifiers.contains(Modifiers::ctrl_or_meta()) => {
|
||||
e.stop_propagation();
|
||||
e.prevent_default();
|
||||
on_submit.call(value.read().clone());
|
||||
false
|
||||
}
|
||||
Key::Named(NamedKey::Escape) | Key::Named(NamedKey::Shift) => true,
|
||||
Key::Named(NamedKey::Tab) => false,
|
||||
_ => {
|
||||
e.stop_propagation();
|
||||
e.prevent_default();
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl Component for TextInput {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let mut expanded = use_state(|| false);
|
||||
let mut area = use_state(Area::default);
|
||||
|
||||
let mut card = rect()
|
||||
.layout(self.layout.clone())
|
||||
.text_style(self.text_style.clone())
|
||||
.children(self.elements.clone());
|
||||
.children(self.elements.clone())
|
||||
.maybe(self.expandable, |el| {
|
||||
el.on_sized(move |e: Event<SizedEventData>| area.set_if_modified(e.area))
|
||||
});
|
||||
|
||||
card.get_style().clone_from(&self.style);
|
||||
|
||||
card.child(
|
||||
self.input
|
||||
.clone()
|
||||
let mut input = self.input.clone();
|
||||
if self.multiline {
|
||||
if let Some(on_submit) = self.on_submit.clone() {
|
||||
input =
|
||||
input.on_pre_key_down(submit_on_modifier_enter(self.value.clone(), on_submit));
|
||||
}
|
||||
if !matches!(self.layout.height, Size::Inner) {
|
||||
input = input.height(Size::fill());
|
||||
}
|
||||
}
|
||||
|
||||
let expanded_input = (self.expandable && expanded()).then(|| {
|
||||
let area = area();
|
||||
let (left, top) = clamp_to_window(
|
||||
area.max_x() - EXPANDED_WIDTH,
|
||||
area.min_y(),
|
||||
EXPANDED_WIDTH,
|
||||
EXPANDED_HEIGHT,
|
||||
);
|
||||
|
||||
OverlayPopup::new()
|
||||
.backdrop(false)
|
||||
.position(Position::new_global().left(left).top(top))
|
||||
.on_close(move |_| expanded.set(false))
|
||||
.child(
|
||||
rect()
|
||||
.corner_radius(CornerRadius::new_all(8.))
|
||||
.shadow(Shadow::from((
|
||||
0.,
|
||||
8.,
|
||||
32.,
|
||||
0.,
|
||||
Color::from_argb(120, 0, 0, 0),
|
||||
)))
|
||||
.child(
|
||||
self.input
|
||||
.clone()
|
||||
.multiline(true)
|
||||
.auto_focus(true)
|
||||
.width(Size::px(EXPANDED_WIDTH))
|
||||
.height(Size::px(EXPANDED_HEIGHT)),
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
if self.expandable {
|
||||
input = input.trailing(
|
||||
rect()
|
||||
.on_pointer_down(|e: Event<PointerEventData>| e.stop_propagation())
|
||||
.child(
|
||||
Button::new()
|
||||
.ghost()
|
||||
.icon()
|
||||
.width(Size::px(22.))
|
||||
.height(Size::px(22.))
|
||||
.padding(3.)
|
||||
.tooltip("Expand")
|
||||
.on_press(move |e: Event<PressEventData>| {
|
||||
e.stop_propagation();
|
||||
expanded.set(true);
|
||||
})
|
||||
.child(
|
||||
Icon::new(IconType::Maximize01)
|
||||
.size(14.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
card.maybe_child(expanded_input).child(
|
||||
input
|
||||
.map(self.text_style.text_align, |mut el, text_align| {
|
||||
el = el.text_align(text_align);
|
||||
|
||||
|
|
|
|||
|
|
@ -627,6 +627,7 @@ impl Component for TextRow {
|
|||
|
||||
let control = TextInput::new(text)
|
||||
.placeholder(placeholder)
|
||||
.expandable(true)
|
||||
.width(Size::px(220.));
|
||||
|
||||
settings_row(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use crate::notifications::{
|
|||
OptionalModsGroup,
|
||||
};
|
||||
use crate::routes::Route;
|
||||
use crate::theme::colors;
|
||||
use crate::theme::{self, colors};
|
||||
use crate::ui::border_all_color;
|
||||
|
||||
type SqlResult = Option<Result<ConsoleQueryResult, String>>;
|
||||
|
|
@ -1093,25 +1093,37 @@ impl Component for SqlConsole {
|
|||
.vertical()
|
||||
.width(Size::fill())
|
||||
.spacing(10.)
|
||||
.child(
|
||||
TextInput::new(query)
|
||||
.multiline(true)
|
||||
.placeholder("SELECT * FROM …")
|
||||
.font_family(theme::MONO_FONT)
|
||||
.width(Size::fill())
|
||||
.height(Size::px(140.))
|
||||
.on_submit(move |_| run_sql(query, result, running)),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(12.)
|
||||
.child(
|
||||
rect().width(Size::flex(1.0)).child(
|
||||
TextInput::new(query)
|
||||
.placeholder("SELECT * FROM …")
|
||||
.on_submit(move |_| run_sql(query, result, running)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new()
|
||||
.primary()
|
||||
.child(Icon::new(IconType::Terminal).size(16.))
|
||||
.text(if *running.read() { "Running…" } else { "Run" })
|
||||
.on_press(move |_| run_sql(query, result, running)),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(if cfg!(target_os = "macos") {
|
||||
"⌘ Enter"
|
||||
} else {
|
||||
"Ctrl+Enter"
|
||||
})
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.child(sql_result(&result.read()))
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ impl Component for SettingsMinecraft {
|
|||
"Extra arguments passed to Java. Separate them with spaces; quote values containing spaces.",
|
||||
TextInput::new(jvm_args)
|
||||
.placeholder("-XX:+UseG1GC")
|
||||
.expandable(true)
|
||||
.width(Size::px(220.)),
|
||||
))
|
||||
.child(section_header("CONTENT"))
|
||||
|
|
@ -127,6 +128,7 @@ impl Component for SettingsMinecraft {
|
|||
"Command to run before launching the game.",
|
||||
TextInput::new(pre_launch_command)
|
||||
.placeholder("echo 'Game started'")
|
||||
.expandable(true)
|
||||
.width(Size::px(220.)),
|
||||
))
|
||||
.child(settings_row(
|
||||
|
|
@ -135,6 +137,7 @@ impl Component for SettingsMinecraft {
|
|||
"Command to run when launching the game.",
|
||||
TextInput::new(wrapper_command)
|
||||
.placeholder("gamescope")
|
||||
.expandable(true)
|
||||
.width(Size::px(220.)),
|
||||
))
|
||||
.child(settings_row(
|
||||
|
|
@ -143,6 +146,7 @@ impl Component for SettingsMinecraft {
|
|||
"Command to run after exiting the game.",
|
||||
TextInput::new(post_exit_command)
|
||||
.placeholder("echo 'Game exited'")
|
||||
.expandable(true)
|
||||
.width(Size::px(220.)),
|
||||
));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue