Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
K
Kayak UI 0.11
Manage
Activity
Members
Labels
Plan
Issues
0
Issue boards
Milestones
Code
Merge requests
0
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Microhacks
Bevy Forks
Kayak UI 0.11
Commits
0b25cb59
Commit
0b25cb59
authored
2 years ago
by
StarToaster
Browse files
Options
Downloads
Patches
Plain Diff
Added cursor for text boxes.
parent
98b4de46
No related branches found
No related tags found
No related merge requests found
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
kayak_font/Cargo.toml
+1
-1
1 addition, 1 deletion
kayak_font/Cargo.toml
kayak_font/src/font.rs
+5
-0
5 additions, 0 deletions
kayak_font/src/font.rs
src/widgets/mod.rs
+2
-1
2 additions, 1 deletion
src/widgets/mod.rs
src/widgets/text_box.rs
+192
-7
192 additions, 7 deletions
src/widgets/text_box.rs
with
200 additions
and
9 deletions
kayak_font/Cargo.toml
+
1
−
1
View file @
0b25cb59
...
@@ -10,7 +10,7 @@ bevy_renderer = ["bevy"]
...
@@ -10,7 +10,7 @@ bevy_renderer = ["bevy"]
[dependencies]
[dependencies]
anyhow
=
{
version
=
"1.0"
}
anyhow
=
{
version
=
"1.0"
}
nanoserde
=
"0.1.30"
nanoserde
=
"0.1.30"
unicode-segmentation
=
"1.
9
"
unicode-segmentation
=
"1.
10.0
"
# Provides UAX #14 line break segmentation
# Provides UAX #14 line break segmentation
xi-unicode
=
"0.3"
xi-unicode
=
"0.3"
...
...
This diff is collapsed.
Click to expand it.
kayak_font/src/font.rs
+
5
−
0
View file @
0b25cb59
...
@@ -92,6 +92,11 @@ impl KayakFont {
...
@@ -92,6 +92,11 @@ impl KayakFont {
width
width
}
}
/// Splits up the provided &str into grapheme clusters.
pub
fn
get_graphemes
<
'a
>
(
&
'a
self
,
content
:
&
'a
str
)
->
Vec
<&
'a
str
>
{
UnicodeSegmentation
::
graphemes
(
content
,
true
)
.collect
::
<
Vec
<
_
>>
()
}
/// Measures the given text content and calculates an appropriate layout
/// Measures the given text content and calculates an appropriate layout
/// given a set of properties.
/// given a set of properties.
///
///
...
...
This diff is collapsed.
Click to expand it.
src/widgets/mod.rs
+
2
−
1
View file @
0b25cb59
...
@@ -86,7 +86,8 @@ pub struct KayakWidgets;
...
@@ -86,7 +86,8 @@ pub struct KayakWidgets;
impl
Plugin
for
KayakWidgets
{
impl
Plugin
for
KayakWidgets
{
fn
build
(
&
self
,
app
:
&
mut
bevy
::
prelude
::
App
)
{
fn
build
(
&
self
,
app
:
&
mut
bevy
::
prelude
::
App
)
{
app
.add_startup_system_to_stage
(
StartupStage
::
PostStartup
,
add_widget_systems
);
app
.add_startup_system_to_stage
(
StartupStage
::
PostStartup
,
add_widget_systems
)
.add_system
(
text_box
::
cursor_animation_system
);
}
}
}
}
...
...
This diff is collapsed.
Click to expand it.
src/widgets/text_box.rs
+
192
−
7
View file @
0b25cb59
use
bevy
::
prelude
::{
Bundle
,
Color
,
Commands
,
Component
,
Entity
,
In
,
Query
};
use
std
::
time
::
Instant
;
use
kayak_ui_macros
::
rsx
;
use
bevy
::
prelude
::
*
;
use
kayak_font
::{
KayakFont
,
TextProperties
};
use
kayak_ui_macros
::{
constructor
,
rsx
};
use
crate
::{
use
crate
::{
context
::
WidgetName
,
context
::
WidgetName
,
...
@@ -8,6 +11,7 @@ use crate::{
...
@@ -8,6 +11,7 @@ use crate::{
on_event
::
OnEvent
,
on_event
::
OnEvent
,
on_layout
::
OnLayout
,
on_layout
::
OnLayout
,
prelude
::{
KChildren
,
KayakWidgetContext
,
OnChange
},
prelude
::{
KChildren
,
KayakWidgetContext
,
OnChange
},
render
::
font
::
FontMapping
,
styles
::{
Edge
,
KStyle
,
RenderCommand
,
StyleProp
,
Units
},
styles
::{
Edge
,
KStyle
,
RenderCommand
,
StyleProp
,
Units
},
widget
::
Widget
,
widget
::
Widget
,
widget_state
::
WidgetState
,
widget_state
::
WidgetState
,
...
@@ -15,7 +19,7 @@ use crate::{
...
@@ -15,7 +19,7 @@ use crate::{
text
::{
TextProps
,
TextWidgetBundle
},
text
::{
TextProps
,
TextWidgetBundle
},
BackgroundBundle
,
ClipBundle
,
BackgroundBundle
,
ClipBundle
,
},
},
Focusable
,
Focusable
,
DEFAULT_FONT
,
};
};
/// Props used by the [`TextBox`] widget
/// Props used by the [`TextBox`] widget
...
@@ -32,9 +36,27 @@ pub struct TextBoxProps {
...
@@ -32,9 +36,27 @@ pub struct TextBoxProps {
pub
value
:
String
,
pub
value
:
String
,
}
}
#[derive(Component,
Default,
Clone,
PartialEq)]
#[derive(Component,
Clone,
PartialEq)]
pub
struct
TextBoxState
{
pub
struct
TextBoxState
{
pub
focused
:
bool
,
pub
focused
:
bool
,
pub
graphemes
:
Vec
<
String
>
,
pub
cursor_x
:
f32
,
pub
cursor_position
:
usize
,
pub
cursor_visible
:
bool
,
pub
cursor_last_update
:
Instant
,
}
impl
Default
for
TextBoxState
{
fn
default
()
->
Self
{
Self
{
focused
:
Default
::
default
(),
graphemes
:
Default
::
default
(),
cursor_x
:
0.0
,
cursor_position
:
Default
::
default
(),
cursor_visible
:
Default
::
default
(),
cursor_last_update
:
Instant
::
now
(),
}
}
}
}
pub
struct
TextBoxValue
(
pub
String
);
pub
struct
TextBoxValue
(
pub
String
);
...
@@ -80,7 +102,9 @@ pub fn text_box_render(
...
@@ -80,7 +102,9 @@ pub fn text_box_render(
let
state_entity
=
widget_context
.use_state
::
<
TextBoxState
>
(
let
state_entity
=
widget_context
.use_state
::
<
TextBoxState
>
(
&
mut
commands
,
&
mut
commands
,
entity
,
entity
,
TextBoxState
::
default
(),
TextBoxState
{
..
TextBoxState
::
default
()
},
);
);
if
let
Ok
(
state
)
=
state_query
.get
(
state_entity
)
{
if
let
Ok
(
state
)
=
state_query
.get
(
state_entity
)
{
...
@@ -119,6 +143,8 @@ pub fn text_box_render(
...
@@ -119,6 +143,8 @@ pub fn text_box_render(
let
current_value
=
text_box
.value
.clone
();
let
current_value
=
text_box
.value
.clone
();
let
cloned_on_change
=
on_change
.clone
();
let
cloned_on_change
=
on_change
.clone
();
let
style_font
=
styles
.font
.clone
();
*
on_event
=
OnEvent
::
new
(
*
on_event
=
OnEvent
::
new
(
move
|
In
((
event_dispatcher_context
,
_
,
mut
event
,
_entity
)):
In
<
(
move
|
In
((
event_dispatcher_context
,
_
,
mut
event
,
_entity
)):
In
<
(
EventDispatcherContext
,
EventDispatcherContext
,
...
@@ -126,8 +152,38 @@ pub fn text_box_render(
...
@@ -126,8 +152,38 @@ pub fn text_box_render(
Event
,
Event
,
Entity
,
Entity
,
)
>
,
)
>
,
font_assets
:
Res
<
Assets
<
KayakFont
>>
,
font_mapping
:
Res
<
FontMapping
>
,
mut
state_query
:
Query
<&
mut
TextBoxState
>
|
{
mut
state_query
:
Query
<&
mut
TextBoxState
>
|
{
match
event
.event_type
{
match
event
.event_type
{
EventType
::
KeyDown
(
key_event
)
=>
{
if
key_event
.key
()
==
KeyCode
::
Right
{
if
let
Ok
(
mut
state
)
=
state_query
.get_mut
(
state_entity
)
{
if
state
.cursor_position
<
state
.graphemes
.len
()
{
state
.cursor_position
+=
1
;
}
set_new_cursor_position
(
&
mut
state
,
&
font_assets
,
&
font_mapping
,
&
style_font
,
);
}
}
if
key_event
.key
()
==
KeyCode
::
Left
{
if
let
Ok
(
mut
state
)
=
state_query
.get_mut
(
state_entity
)
{
if
state
.cursor_position
>
0
{
state
.cursor_position
-=
1
;
}
set_new_cursor_position
(
&
mut
state
,
&
font_assets
,
&
font_mapping
,
&
style_font
,
);
}
}
}
EventType
::
CharInput
{
c
}
=>
{
EventType
::
CharInput
{
c
}
=>
{
let
mut
current_value
=
current_value
.clone
();
let
mut
current_value
=
current_value
.clone
();
let
cloned_on_change
=
cloned_on_change
.clone
();
let
cloned_on_change
=
cloned_on_change
.clone
();
...
@@ -140,17 +196,61 @@ pub fn text_box_render(
...
@@ -140,17 +196,61 @@ pub fn text_box_render(
}
}
if
is_backspace
(
c
)
{
if
is_backspace
(
c
)
{
if
!
current_value
.is_empty
()
{
if
!
current_value
.is_empty
()
{
current_value
.truncate
(
current_value
.len
()
-
1
);
if
let
Ok
(
mut
state
)
=
state_query
.get_mut
(
state_entity
)
{
// TODO: This doesn't respect graphemes!
current_value
.remove
(
state
.cursor_position
-
1
);
state
.cursor_position
-=
1
;
}
}
}
}
else
if
!
c
.is_control
()
{
}
else
if
!
c
.is_control
()
{
current_value
.push
(
c
);
if
let
Ok
(
mut
state
)
=
state_query
.get_mut
(
state_entity
)
{
// TODO: This doesn't respect graphemes!
current_value
.insert
(
state
.cursor_position
,
c
);
state
.cursor_position
+=
1
;
}
}
if
let
Ok
(
mut
state
)
=
state_query
.get_mut
(
state_entity
)
{
// Update graphemes
set_graphemes
(
&
mut
state
,
&
font_assets
,
&
font_mapping
,
&
style_font
,
&
current_value
,
);
set_new_cursor_position
(
&
mut
state
,
&
font_assets
,
&
font_mapping
,
&
style_font
,
);
}
}
cloned_on_change
.set_value
(
current_value
);
cloned_on_change
.set_value
(
current_value
);
event
.add_system
(
cloned_on_change
);
event
.add_system
(
cloned_on_change
);
}
}
EventType
::
Focus
=>
{
EventType
::
Focus
=>
{
if
let
Ok
(
mut
state
)
=
state_query
.get_mut
(
state_entity
)
{
if
let
Ok
(
mut
state
)
=
state_query
.get_mut
(
state_entity
)
{
state
.focused
=
true
;
state
.focused
=
true
;
// Update graphemes
set_graphemes
(
&
mut
state
,
&
font_assets
,
&
font_mapping
,
&
style_font
,
&
current_value
,
);
state
.cursor_position
=
state
.graphemes
.len
();
set_new_cursor_position
(
&
mut
state
,
&
font_assets
,
&
font_mapping
,
&
style_font
,
);
}
}
}
}
EventType
::
Blur
=>
{
EventType
::
Blur
=>
{
...
@@ -164,6 +264,16 @@ pub fn text_box_render(
...
@@ -164,6 +264,16 @@ pub fn text_box_render(
},
},
);
);
let
cursor_styles
=
KStyle
{
background_color
:
Color
::
rgba
(
0.933
,
0.745
,
0.745
,
1.0
)
.into
(),
position_type
:
crate
::
styles
::
KPositionType
::
SelfDirected
.into
(),
top
:
Units
::
Pixels
(
5.0
)
.into
(),
left
:
Units
::
Pixels
(
state
.cursor_x
)
.into
(),
width
:
Units
::
Pixels
(
2.0
)
.into
(),
height
:
Units
::
Pixels
(
26.0
-
10.0
)
.into
(),
..
Default
::
default
()
};
let
parent_id
=
Some
(
entity
);
let
parent_id
=
Some
(
entity
);
rsx!
{
rsx!
{
<
BackgroundBundle
styles
=
{
background_styles
}
>
<
BackgroundBundle
styles
=
{
background_styles
}
>
...
@@ -188,6 +298,13 @@ pub fn text_box_render(
...
@@ -188,6 +298,13 @@ pub fn text_box_render(
..
Default
::
default
()
..
Default
::
default
()
}}
}}
/>
/>
{
if
state
.focused
&&
state
.cursor_visible
{
constructor!
{
<
BackgroundBundle
styles
=
{
cursor_styles
}
/>
}
}
}
</
ClipBundle
>
</
ClipBundle
>
</
BackgroundBundle
>
</
BackgroundBundle
>
}
}
...
@@ -203,3 +320,71 @@ pub fn text_box_render(
...
@@ -203,3 +320,71 @@ pub fn text_box_render(
fn
is_backspace
(
c
:
char
)
->
bool
{
fn
is_backspace
(
c
:
char
)
->
bool
{
c
==
'\u{8}'
||
c
==
'\u{7f}'
c
==
'\u{8}'
||
c
==
'\u{7f}'
}
}
fn
set_graphemes
(
state
:
&
mut
TextBoxState
,
font_assets
:
&
Res
<
Assets
<
KayakFont
>>
,
font_mapping
:
&
FontMapping
,
style_font
:
&
StyleProp
<
String
>
,
current_value
:
&
String
,
)
{
let
font_handle
=
match
style_font
{
StyleProp
::
Value
(
font
)
=>
font_mapping
.get_handle
(
font
.clone
())
.unwrap
(),
_
=>
font_mapping
.get_handle
(
DEFAULT_FONT
.into
())
.unwrap
(),
};
if
let
Some
(
font
)
=
font_assets
.get
(
&
font_handle
)
{
state
.graphemes
=
font
.get_graphemes
(
&
current_value
)
.iter
()
.map
(|
s
|
s
.to_string
())
.collect
::
<
Vec
<
_
>>
();
}
}
fn
set_new_cursor_position
(
state
:
&
mut
TextBoxState
,
font_assets
:
&
Res
<
Assets
<
KayakFont
>>
,
font_mapping
:
&
FontMapping
,
style_font
:
&
StyleProp
<
String
>
,
)
{
let
font_handle
=
match
style_font
{
StyleProp
::
Value
(
font
)
=>
font_mapping
.get_handle
(
font
.clone
())
.unwrap
(),
_
=>
font_mapping
.get_handle
(
DEFAULT_FONT
.into
())
.unwrap
(),
};
if
let
Some
(
font
)
=
font_assets
.get
(
&
font_handle
)
{
let
string_to_cursor
=
state
.graphemes
[
0
..
state
.cursor_position
]
.join
(
""
);
let
measurement
=
font
.measure
(
&
string_to_cursor
,
TextProperties
{
font_size
:
14.0
,
line_height
:
18.0
,
max_size
:
(
10000.0
,
18.0
),
alignment
:
kayak_font
::
Alignment
::
Start
,
tab_size
:
4
,
},
);
state
.cursor_x
=
measurement
.size
()
.0
;
}
}
pub
fn
cursor_animation_system
(
mut
state_query
:
ParamSet
<
(
Query
<
(
Entity
,
&
TextBoxState
)
>
,
Query
<&
mut
TextBoxState
>
)
>
,
)
{
let
mut
should_update
=
Vec
::
new
();
for
(
entity
,
state
)
in
state_query
.p0
()
.iter
()
{
if
state
.cursor_last_update
.elapsed
()
.as_secs_f32
()
>
0.5
&&
state
.focused
{
should_update
.push
(
entity
);
}
}
for
state_entity
in
should_update
.drain
(
..
)
{
if
let
Ok
(
mut
state
)
=
state_query
.p1
()
.get_mut
(
state_entity
)
{
state
.cursor_last_update
=
Instant
::
now
();
state
.cursor_visible
=
!
state
.cursor_visible
;
}
}
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment