Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple single line text field #69

Draft
wants to merge 42 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
39eee15
Text field hover
rewin123 Oct 6, 2024
968fb2d
Cursor support
rewin123 Oct 6, 2024
20c7f9e
utf-8 support and split lib file
rewin123 Oct 7, 2024
0d715a9
cursor position based on mouse position
rewin123 Oct 7, 2024
5c2be00
Focus support
rewin123 Oct 7, 2024
9244721
Fix focus managment for input fields
rewin123 Oct 7, 2024
7d8996b
fmt
rewin123 Oct 7, 2024
8a17db5
Support text longer that text field
rewin123 Oct 8, 2024
081d115
ctrl-c and ctrl-v support
rewin123 Oct 8, 2024
6eccf19
Delete key support
rewin123 Oct 8, 2024
68ba56d
fix cursor size
rewin123 Oct 8, 2024
6c51259
partial selection support
rewin123 Oct 8, 2024
5d93681
clear code from warnings
rewin123 Oct 8, 2024
940f469
simple numeric field
rewin123 Oct 8, 2024
513dc80
dragging support
rewin123 Oct 8, 2024
02cb5f4
Merge remote-tracking branch 'upstream/main'
rewin123 Oct 8, 2024
7cd53b3
clear warnings
rewin123 Oct 8, 2024
6dec384
less traits for numeric field value
rewin123 Oct 8, 2024
f10faef
fix set value bug for numeric field
rewin123 Oct 8, 2024
7dca22c
despawning required components
rewin123 Oct 8, 2024
eb0a12c
fmt
rewin123 Oct 8, 2024
fea1f11
fix overflow problem in numeric fields
rewin123 Oct 8, 2024
736fc30
Fix highlightning bug
rewin123 Oct 8, 2024
9b6f1fb
fix warning
rewin123 Oct 8, 2024
6c78dc0
theme support
rewin123 Oct 8, 2024
2caa650
fix bug in ctrl-a
rewin123 Oct 8, 2024
9ed5e68
fix rare bug in clicking on non-english text
rewin123 Oct 8, 2024
34fd148
fmt
rewin123 Oct 8, 2024
fe70f6d
typos
rewin123 Oct 8, 2024
971ff88
clippy
rewin123 Oct 8, 2024
79e9861
clippy
rewin123 Oct 8, 2024
26b9e01
clippy
rewin123 Oct 8, 2024
886234b
clippy
rewin123 Oct 8, 2024
fb8cde7
fmt
rewin123 Oct 8, 2024
98449de
fix bug with float drag
rewin123 Oct 8, 2024
1129e8d
Add macro for floats, readme, and unuify default using
rewin123 Oct 13, 2024
aa031bb
separate clipboard to own crate
rewin123 Oct 14, 2024
7280483
fmt
rewin123 Oct 15, 2024
373f161
a
rewin123 Oct 15, 2024
19ac220
Merge remote-tracking branch 'upstream/main' into moving-to-bevy-vers…
rewin123 Oct 15, 2024
4d5fd1c
update undo/redo to last bevy version
rewin123 Oct 15, 2024
9218104
all broken, but compiled
rewin123 Oct 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ bevy_i-cant-believe-its-not-bsn = { path = "bevy_widgets/bevy_i-cant-believe-its
bevy_menu_bar = { path = "bevy_widgets/bevy_menu_bar" }
bevy_toolbar = { path = "bevy_widgets/bevy_toolbar" }
bevy_tooltips = { path = "bevy_widgets/bevy_tooltips" }
bevy_text_field = { path = "bevy_widgets/bevy_text_field" }
bevy_numeric_field = { path = "bevy_widgets/bevy_numeric_field" }
bevy_focus = { path = "bevy_widgets/bevy_focus" }
bevy_text_editing = { path = "bevy_widgets/bevy_text_editing" }

# general crates
bevy_asset_preview = { path = "crates/bevy_asset_preview" }
Expand All @@ -59,4 +63,5 @@ bevy_editor_styles = { path = "crates/bevy_editor_styles" }
bevy_localization = { path = "crates/bevy_localization" }
bevy_pane_layout = { path = "crates/bevy_pane_layout" }
bevy_transform_gizmos = { path = "crates/bevy_transform_gizmos" }
bevy_undo = { path = "crates/bevy_undo" }
bevy_undo = { path = "crates/bevy_undo" }
bevy_clipboard = { path = "crates/bevy_clipboard" }
10 changes: 10 additions & 0 deletions bevy_widgets/bevy_focus/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "bevy_focus"
version = "0.1.0"
edition = "2021"

[dependencies]
bevy.workspace = true

[lints]
workspace = true
134 changes: 134 additions & 0 deletions bevy_widgets/bevy_focus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//! This crate contains input focus management for Bevy widgets.
//! Currently only one entity can hold focus at a time.

use bevy::prelude::*;

/// Plugin for input focus logic
pub struct FocusPlugin;

impl Plugin for FocusPlugin {
fn build(&self, app: &mut App) {
app.add_event::<SetFocus>();
app.add_event::<ClearFocus>();
app.add_event::<GotFocus>();
app.add_event::<LostFocus>();

app.add_observer(set_focus);
app.add_observer(clear_focus);
app.add_observer(mouse_click);

app.add_systems(
Last,
clear_focus_after_click.run_if(resource_exists::<NeedClearFocus>),
);
}
}

/// Component which indicates that a widget is focused and can receive input events.
#[derive(Component)]
pub struct Focus;

/// Mark that a widget can receive input events and can be focused
#[derive(Component)]
pub struct Focusable;

/// Event indicating that a widget has received focus
#[derive(Event)]
pub struct GotFocus(pub Option<Pointer<Click>>);

/// Event indicating that a widget has lost focus
#[derive(Event)]
pub struct LostFocus;

/// Set focus to a widget
#[derive(Event)]
pub struct SetFocus;

/// Clear focus from widgets
#[derive(Event)]
pub struct ClearFocus;

/// Extension trait for [`Commands`]
/// Contains commands to set and clear input focus
pub trait FocusExt {
/// Set input focus to the given targets
fn set_focus(&mut self, target: Entity);

/// Clear input focus
fn clear_focus(&mut self);
}

impl FocusExt for Commands<'_, '_> {
fn set_focus(&mut self, target: Entity) {
self.trigger_targets(SetFocus, target);
}

fn clear_focus(&mut self) {
self.trigger(ClearFocus);
}
}

#[derive(Resource)]
struct NeedClearFocus(bool);

fn set_focus(
trigger: Trigger<SetFocus>,
mut commands: Commands,
q_focused: Query<Entity, With<Focus>>,
) {
for entity in q_focused.iter() {
if entity == trigger.entity() {
continue;
}
commands.entity(entity).remove::<Focus>();
commands.trigger_targets(LostFocus, entity);
}
commands.entity(trigger.entity()).insert(Focus);
commands.trigger_targets(GotFocus(None), trigger.entity());
}

fn clear_focus(
_: Trigger<ClearFocus>,
mut commands: Commands,
q_focused: Query<Entity, With<Focus>>,
) {
for entity in q_focused.iter() {
commands.entity(entity).insert(Focus);
commands.trigger_targets(LostFocus, entity);
}
}

fn mouse_click(
mut click: Trigger<Pointer<Click>>,
mut commands: Commands,
q_focusable: Query<Entity, With<Focusable>>,
q_focused: Query<Entity, With<Focus>>,
) {
if click.event().button != PointerButton::Primary {
return;
}
let entity = click.entity();
if q_focusable.contains(entity) {
commands.insert_resource(NeedClearFocus(false));

click.propagate(false);
for e in q_focused.iter() {
if e == entity {
continue;
}
commands.entity(e).remove::<Focus>();
commands.trigger_targets(LostFocus, e);
}
commands.entity(entity).insert(Focus);
commands.trigger_targets(GotFocus(Some(click.event().clone())), entity);
} else {
commands.insert_resource(NeedClearFocus(true));
}
}

fn clear_focus_after_click(mut commands: Commands, need_clear_focus: Res<NeedClearFocus>) {
if need_clear_focus.0 {
commands.clear_focus();
commands.remove_resource::<NeedClearFocus>();
}
}
13 changes: 13 additions & 0 deletions bevy_widgets/bevy_numeric_field/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "bevy_numeric_field"
version = "0.1.0"
edition = "2021"

[dependencies]
bevy.workspace = true
bevy_text_field.workspace = true
bevy_focus.workspace = true
num-traits = "0.2.19"

[lints]
workspace = true
102 changes: 102 additions & 0 deletions bevy_widgets/bevy_numeric_field/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# bevy_numeric_field

A flexible and feature-rich numeric input field widget for the Bevy game engine.

## Overview

`bevy_numeric_field` provides a customizable numeric input widget for Bevy applications, offering a robust solution for inputting and manipulating numeric values with support for various numeric types, including integers and floating-point numbers.

## Features

- Support for multiple numeric types (u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64)
- Mouse cursor positioning and text selection
- Clipboard operations (copy, cut, paste, select all via Ctrl+C, X, V, A)
- Drag functionality for incrementing/decrementing values
- Customizable min/max value constraints
- Efficient rendering and update systems

## Installation

Add `bevy_numeric_field` to your `Cargo.toml`:

```toml
[dependencies]
bevy_numeric_field = "0.1.0"
```

## Usage

Here's a basic example of how to use `bevy_numeric_field` in your Bevy app:

```rust
use bevy::prelude::*;
use bevy_numeric_field::{NumericField, NumericFieldPlugin, NewValue};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(DefaultNumericFieldPlugin)
.add_systems(Startup, setup)
.run();
}

fn setup(mut commands: Commands) {
commands.spawn((
NodeBundle {
style: Style {
width: Val::Px(200.0),
height: Val::Px(30.0),
..Default::default()
},
..Default::default()
},
NumericField::<f32>::new(0.0),
)).observe(|trigger: Trigger<NewValue<f32>>| {
let value = trigger.event().0;
println!("Value changed: {}", value);
});
}
```

### Setting Constraints

You can set minimum and maximum values for the numeric field:

```rust
fn setup_constrained_field(mut commands: Commands) {
let mut numeric_field = NumericField::<i32>::new(0);
numeric_field.min = Some(-100);
numeric_field.max = Some(100);

commands.spawn((
NodeBundle {
style: Style {
width: Val::Px(100.0),
height: Val::Px(30.0),
..Default::default()
},
..Default::default()
},
numeric_field,
));
}
```

## How It Works

`bevy_numeric_field` builds upon the `bevy_text_field` crate to create a specialized input widget for numeric values. Here's a breakdown of its core components and systems:

1. **Input Handling**:
- Text input is parsed and validated against the specified numeric type.
- Drag operations allow for incremental value changes.

2. **Constraints**:
- Optional minimum and maximum values can be set to restrict input.

3. **Events**:
- The `NewValue` event is emitted when the numeric value changes.
- The `SetValue` event can be used to programmatically update the field's value.

4. **Rendering**:
- The widget leverages `bevy_text_field` for text rendering and cursor management.
- Additional visual feedback is provided for invalid inputs.
111 changes: 111 additions & 0 deletions bevy_widgets/bevy_numeric_field/examples/many_fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Contains example for spawning many numeric fields

use bevy::prelude::*;
use bevy_numeric_field::*;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(DefaultNumericFieldPlugin)
.add_systems(Startup, setup)
.run();
}

fn setup(mut commands: Commands) {
commands.spawn(Camera {
clear_color: ClearColorConfig::Custom(Color::srgb(
34.0 / 255.0,
34.0 / 255.0,
34.0 / 255.0,
)),
..Default::default()
});

commands
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
flex_direction: FlexDirection::Row,
display: Display::Flex,
..Default::default()
rewin123 marked this conversation as resolved.
Show resolved Hide resolved
},
..Default::default()
})
.with_children(|parent| {
// Unsigned integers column
parent
.spawn(NodeBundle {
style: Style {
display: Display::Grid,
grid_template_columns: vec![GridTrack::auto(), GridTrack::auto()],
align_items: AlignItems::Center,
margin: UiRect::all(Val::Px(10.0)),
..Default::default()
},
..Default::default()
})
.with_children(|column| {
spawn_numeric_field::<u8>(column, "u8");
spawn_numeric_field::<u16>(column, "u16");
spawn_numeric_field::<u32>(column, "u32");
spawn_numeric_field::<u64>(column, "u64");
spawn_numeric_field::<u128>(column, "u128");
});

// Signed integers column
parent
.spawn(NodeBundle {
style: Style {
display: Display::Grid,
grid_template_columns: vec![GridTrack::auto(), GridTrack::auto()],
align_items: AlignItems::Center,
margin: UiRect::all(Val::Px(10.0)),
..Default::default()
},
..Default::default()
})
.with_children(|column| {
spawn_numeric_field::<i8>(column, "i8");
spawn_numeric_field::<i16>(column, "i16");
spawn_numeric_field::<i32>(column, "i32");
spawn_numeric_field::<i64>(column, "i64");
spawn_numeric_field::<i128>(column, "i128");
});

// Floating-point numbers column
parent
.spawn(NodeBundle {
style: Style {
display: Display::Grid,
grid_template_columns: vec![GridTrack::auto(), GridTrack::auto()],
align_items: AlignItems::Center,
margin: UiRect::all(Val::Px(10.0)),
..Default::default()
},
..Default::default()
})
.with_children(|column| {
spawn_numeric_field::<f32>(column, "f32");
spawn_numeric_field::<f64>(column, "f64");
});
});
}

fn spawn_numeric_field<T: NumericFieldValue + Default>(row: &mut ChildBuilder, label: &str) {
row.spawn(Text(label.to_string()));
row.spawn((
NodeBundle {
style: Style {
width: Val::Px(150.0),
height: Val::Px(30.0),
margin: UiRect::left(Val::Px(10.0)),
..Default::default()
},
..Default::default()
},
NumericField::<T>::new(T::default()),
));
}
Loading
Loading