-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Emoji picker/selector example (#420)
![Xilem Emoji Picker.](https://github.com/linebender/xilem/assets/36049421/4f9b418f-c9b3-4971-9711-587f21010e47) Some notes: 1) The accessibility of this example probably isn't great. Not sure what to do about this. 2) Our layout primitives aren't great; we use a grid, but ideally the number of rows would be reactive to the available space. 3) The pagination is slightly hacked together - it should really try and give you page numbers. I'm not planning to address this, unless someone provides the algorithm This was originally created to act as a screenshot for linebender/linebender.github.io#56
- Loading branch information
Showing
10 changed files
with
579 additions
and
227 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
// Copyright 2024 the Xilem Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! A simple emoji picker. | ||
#![expect(clippy::shadow_unrelated, reason = "Idiomatic for Xilem users")] | ||
|
||
use winit::error::EventLoopError; | ||
use xilem::{ | ||
core::map_state, | ||
palette, | ||
view::{button, flex, grid, label, prose, sized_box, Axis, FlexExt, FlexSpacer, GridExt}, | ||
Color, EventLoop, EventLoopBuilder, WidgetView, Xilem, | ||
}; | ||
|
||
fn app_logic(data: &mut EmojiPagination) -> impl WidgetView<EmojiPagination> { | ||
flex(( | ||
sized_box(flex(()).must_fill_major_axis(true)).height(50.), // Padding because of the info bar on Android | ||
flex(( | ||
// TODO: Expose that this is a "zoom out" button accessibly | ||
button("๐-", |data: &mut EmojiPagination| { | ||
data.size = (data.size + 1).min(5); | ||
}), | ||
// TODO: Expose that this is a "zoom in" button accessibly | ||
button("๐+", |data: &mut EmojiPagination| { | ||
data.size = (data.size - 1).max(2); | ||
}), | ||
)) | ||
.direction(Axis::Horizontal), | ||
picker(data).flex(1.0), | ||
map_state( | ||
paginate( | ||
data.start_index, | ||
(data.size * data.size) as usize, | ||
data.emoji.len(), | ||
), | ||
|state: &mut EmojiPagination| &mut state.start_index, | ||
), | ||
data.last_selected | ||
.map(|idx| label(format!("Selected: {}", data.emoji[idx].display)).text_size(40.)), | ||
FlexSpacer::Fixed(10.), | ||
)) | ||
.direction(Axis::Vertical) | ||
.must_fill_major_axis(true) | ||
} | ||
|
||
fn picker(data: &mut EmojiPagination) -> impl WidgetView<EmojiPagination> { | ||
let mut grid_items = vec![]; | ||
'outer: for y in 0..data.size as usize { | ||
let row_idx = data.start_index + y * data.size as usize; | ||
for x in 0..data.size as usize { | ||
let idx = row_idx + x; | ||
let emoji = data.emoji.get(idx); | ||
let Some(emoji) = emoji else { | ||
// There are no more emoji, no point still looping | ||
break 'outer; | ||
}; | ||
let view = flex(( | ||
// TODO: Expose that this button corresponds to the label below for accessibility? | ||
sized_box(button(emoji.display, move |data: &mut EmojiPagination| { | ||
data.last_selected = Some(idx); | ||
})) | ||
.expand_width(), | ||
sized_box( | ||
prose(emoji.name) | ||
.alignment(xilem::TextAlignment::Middle) | ||
.brush(if data.last_selected.is_some_and(|it| it == idx) { | ||
// TODO: Ensure this selection indicator color is accessible | ||
// TODO: Expose selected state to accessibility tree | ||
palette::css::BLUE | ||
} else { | ||
Color::WHITE | ||
}), | ||
) | ||
.expand_width(), | ||
)) | ||
.must_fill_major_axis(true); | ||
grid_items.push(view.grid_pos(x.try_into().unwrap(), y.try_into().unwrap())); | ||
} | ||
} | ||
|
||
grid( | ||
grid_items, | ||
data.size.try_into().unwrap(), | ||
data.size.try_into().unwrap(), | ||
) | ||
} | ||
|
||
fn paginate( | ||
current_start: usize, | ||
count_per_page: usize, | ||
max_count: usize, | ||
) -> impl WidgetView<usize> { | ||
let percentage = (current_start * 100) / max_count; | ||
|
||
flex(( | ||
// TODO: Expose that this is a previous page button to accessibility | ||
button("<-", move |data| { | ||
*data = current_start.saturating_sub(count_per_page); | ||
}), | ||
label(format!("{percentage}%")), | ||
button("->", move |data| { | ||
let new_idx = current_start + count_per_page; | ||
if new_idx < max_count { | ||
*data = new_idx; | ||
} | ||
}), | ||
)) | ||
.direction(Axis::Horizontal) | ||
} | ||
|
||
struct EmojiPagination { | ||
size: u32, | ||
last_selected: Option<usize>, | ||
start_index: usize, | ||
emoji: Vec<EmojiInfo>, | ||
} | ||
|
||
fn run(event_loop: EventLoopBuilder) -> Result<(), EventLoopError> { | ||
let emoji = EmojiInfo::parse_file(); | ||
let data = EmojiPagination { | ||
size: 4, | ||
last_selected: None, | ||
start_index: 0, | ||
emoji, | ||
}; | ||
|
||
let app = Xilem::new(data, app_logic); | ||
app.run_windowed(event_loop, "First Example".into()) | ||
} | ||
|
||
struct EmojiInfo { | ||
name: &'static str, | ||
display: &'static str, | ||
} | ||
|
||
impl EmojiInfo { | ||
/// Parse the supported emoji's information. | ||
fn parse_file() -> Vec<Self> { | ||
let mut lines = EMOJI_NAMES_CSV.lines(); | ||
let first_line = lines.next(); | ||
assert_eq!( | ||
first_line, | ||
Some("display,name"), | ||
"Probably wrong CSV-like file" | ||
); | ||
lines.flat_map(Self::parse_single).collect() | ||
} | ||
|
||
fn parse_single(line: &'static str) -> Option<Self> { | ||
let (display, name) = line.split_once(',')?; | ||
Some(Self { display, name }) | ||
} | ||
} | ||
|
||
/// A subset of emoji data from <https://github.com/iamcal/emoji-data>, used under the MIT license. | ||
/// Full details can be found in `xilem/resources/data/emoji_names/README.md` from | ||
/// the workspace root. | ||
const EMOJI_NAMES_CSV: &str = include_str!(concat!( | ||
env!("CARGO_MANIFEST_DIR"), | ||
"/resources/data/emoji_names/emoji.csv", | ||
)); | ||
|
||
// Boilerplate code: Identical across all applications which support Android | ||
|
||
#[expect(clippy::allow_attributes, reason = "No way to specify the condition")] | ||
#[allow(dead_code, reason = "False positive: needed in not-_android version")] | ||
// This is treated as dead code by the Android version of the example, but is actually live | ||
// This hackery is required because Cargo doesn't care to support this use case, of one | ||
// example which works across Android and desktop | ||
fn main() -> Result<(), EventLoopError> { | ||
run(EventLoop::with_user_event()) | ||
} | ||
#[cfg(target_os = "android")] | ||
// Safety: We are following `android_activity`'s docs here | ||
#[expect( | ||
unsafe_code, | ||
reason = "We believe that there are no other declarations using this name in the compiled objects here" | ||
)] | ||
#[no_mangle] | ||
fn android_main(app: winit::platform::android::activity::AndroidApp) { | ||
use winit::platform::android::EventLoopBuilderExtAndroid; | ||
|
||
let mut event_loop = EventLoop::with_user_event(); | ||
event_loop.with_android_app(app); | ||
|
||
run(event_loop).expect("Can create app"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2013 Cal Henderson | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Emoji names | ||
|
||
The data in emoji.csv was adapted as a subset of <https://github.com/iamcal/emoji-data>. | ||
These were extracted from <https://github.com/iamcal/emoji-data/blob/master/emoji_pretty.json> on 2024-09-03, | ||
specifically as at commit [`a8174c74675355c8c6a9564516b2e961fe7257ef`](https://github.com/iamcal/emoji-data/blob/a8174c74675355c8c6a9564516b2e961fe7257ef/emoji_pretty.json). | ||
Full license text can be found above in the source file. | ||
|
||
## License | ||
|
||
These are licensed solely under the MIT license, as found in [LICENSE](./LICENSE). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
display,name | ||
๐,grinning face with smiling eyes | ||
๐,face with tears of joy | ||
๐,smiling face with open mouth | ||
๐,smiling face with open mouth and smiling eyes | ||
๐ ,smiling face with open mouth and cold sweat | ||
๐,smiling face with open mouth and tightly-closed eyes | ||
๐,smiling face with halo | ||
๐,smiling face with horns | ||
๐,winking face | ||
๐,smiling face with smiling eyes | ||
๐,face savouring delicious food | ||
๐,relieved face | ||
๐,smiling face with heart-shaped eyes | ||
๐,smiling face with sunglasses | ||
๐,smirking face | ||
๐,neutral face | ||
๐,expressionless face | ||
๐,unamused face | ||
๐,face with cold sweat | ||
๐,pensive face | ||
๐,confused face | ||
๐,confounded face | ||
๐,kissing face | ||
๐,face throwing a kiss | ||
๐,kissing face with smiling eyes | ||
๐,kissing face with closed eyes | ||
๐,face with stuck-out tongue | ||
๐,face with stuck-out tongue and winking eye | ||
๐,face with stuck-out tongue and tightly-closed eyes | ||
๐,disappointed face | ||
๐,worried face | ||
๐ ,angry face | ||
๐ก,pouting face | ||
๐ข,crying face | ||
๐ฃ,persevering face | ||
๐ค,face with look of triumph | ||
๐ฅ,disappointed but relieved face | ||
๐ฆ,frowning face with open mouth | ||
๐ง,anguished face | ||
๐จ,fearful face | ||
๐ฉ,weary face | ||
๐ช,sleepy face | ||
๐ซ,tired face | ||
๐ฌ,grimacing face | ||
๐ญ,loudly crying face | ||
๐ฎโ๐จ,face exhaling | ||
๐ฎ,face with open mouth | ||
๐ฏ,hushed face | ||
๐ฐ,face with open mouth and cold sweat | ||
๐ฑ,face screaming in fear | ||
๐ฒ,astonished face | ||
๐ณ,flushed face | ||
๐ด,sleeping face | ||
๐ตโ๐ซ,face with spiral eyes | ||
๐ต,dizzy face | ||
๐ถโ๐ซ๏ธ,face in clouds | ||
๐ถ,face without mouth | ||
๐ท,face with medical mask | ||
๐ธ,grinning cat face with smiling eyes | ||
๐น,cat face with tears of joy | ||
๐บ,smiling cat face with open mouth | ||
๐ป,smiling cat face with heart-shaped eyes | ||
๐ผ,cat face with wry smile | ||
๐ฝ,kissing cat face with closed eyes | ||
๐พ,pouting cat face | ||
๐ฟ,crying cat face | ||
๐,weary cat face | ||
๐,slightly frowning face | ||
๐โโ๏ธ,head shaking horizontally | ||
๐โโ๏ธ,head shaking vertically | ||
๐,slightly smiling face | ||
๐,upside-down face | ||
๐,face with rolling eyes | ||
๐ โโ๏ธ,woman gesturing no | ||
๐ โโ๏ธ,man gesturing no | ||
๐ ,face with no good gesture | ||
๐โโ๏ธ,woman gesturing ok | ||
๐โโ๏ธ,man gesturing ok | ||
๐,face with ok gesture | ||
๐โโ๏ธ,woman bowing | ||
๐โโ๏ธ,man bowing | ||
๐,person bowing deeply | ||
๐,see-no-evil monkey | ||
๐,hear-no-evil monkey | ||
๐,speak-no-evil monkey | ||
๐โโ๏ธ,woman raising hand | ||
๐โโ๏ธ,man raising hand | ||
๐,happy person raising one hand | ||
๐,person raising both hands in celebration | ||
๐โโ๏ธ,woman frowning | ||
๐โโ๏ธ,man frowning | ||
๐,person frowning | ||
๐โโ๏ธ,woman pouting | ||
๐โโ๏ธ,man pouting | ||
๐,person with pouting face | ||
๐,person with folded hands | ||
๐,rocket | ||
๐,helicopter |