diff options
author | Simon Wülker <simon.wuelker@arcor.de> | 2025-04-03 14:11:55 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-03 12:11:55 +0000 |
commit | 0e99539dab4c059ba3c3750cfda42e246ce8b4f0 (patch) | |
tree | a5703fc0ca14d1bc66345b3168d183bab037427a /ports/servoshell/desktop/dialog.rs | |
parent | 6e9d01b908b51d3f44f858476acf3af8c1d44289 (diff) | |
download | servo-0e99539dab4c059ba3c3750cfda42e246ce8b4f0.tar.gz servo-0e99539dab4c059ba3c3750cfda42e246ce8b4f0.zip |
Support single-value `<select>` elements (#35684)
https://github.com/user-attachments/assets/9aba75ff-4190-4a85-89ed-d3f3aa53d3b0
Among other things this adds a new `EmbedderMsg::ShowSelectElementMenu`
to tell the embedder to display a select popup at the given location.
This is a draft because some small style adjustments need to be made:
* the select element should always have the width of the largest option
* the border should be part of the shadow tree
Apart from that, it's mostly ready for review.
<details><summary>HTML for demo video</summary>
```html
<html>
<body>
<select id="c" name="choice">
<option value="first">First Value</option>
<option value="second">Second Value</option>
<option value="third">Third Value</option>
</select>
</body>
</html>
```
</details>
---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by
`[X]` when the step is complete, and replace `___` with appropriate
data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] Part of https://github.com/servo/servo/issues/3551
- [ ] There are tests for these changes OR
- [ ] These changes do not require tests because ___
<!-- Also, please make sure that "Allow edits from maintainers" checkbox
is checked, so that we can help you if you get stuck somewhere along the
way.-->
<!-- Pull requests that do not address these steps are welcome, but they
will require additional verification as part of the review process. -->
---------
Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
Diffstat (limited to 'ports/servoshell/desktop/dialog.rs')
-rw-r--r-- | ports/servoshell/desktop/dialog.rs | 114 |
1 files changed, 113 insertions, 1 deletions
diff --git a/ports/servoshell/desktop/dialog.rs b/ports/servoshell/desktop/dialog.rs index 0ff8d4cd900..2bfccb523de 100644 --- a/ports/servoshell/desktop/dialog.rs +++ b/ports/servoshell/desktop/dialog.rs @@ -7,11 +7,14 @@ use std::sync::Arc; use egui::Modal; use egui_file_dialog::{DialogState, FileDialog as EguiFileDialog}; +use euclid::Length; use log::warn; use servo::ipc_channel::ipc::IpcSender; +use servo::servo_geometry::DeviceIndependentPixel; use servo::{ AlertResponse, AuthenticationRequest, ConfirmResponse, FilterPattern, PermissionRequest, - PromptResponse, SimpleDialog, + PromptResponse, SelectElement, SelectElementOption, SelectElementOptionOrOptgroup, + SimpleDialog, }; pub enum Dialog { @@ -36,6 +39,10 @@ pub enum Dialog { selected_device_index: usize, response_sender: IpcSender<Option<String>>, }, + SelectElement { + maybe_prompt: Option<SelectElement>, + toolbar_offset: Length<f32, DeviceIndependentPixel>, + }, } impl Dialog { @@ -102,6 +109,16 @@ impl Dialog { } } + pub fn new_select_element_dialog( + prompt: SelectElement, + toolbar_offset: Length<f32, DeviceIndependentPixel>, + ) -> Self { + Dialog::SelectElement { + maybe_prompt: Some(prompt), + toolbar_offset, + } + } + pub fn update(&mut self, ctx: &egui::Context) -> bool { match self { Dialog::File { @@ -373,6 +390,101 @@ impl Dialog { }); is_open }, + Dialog::SelectElement { + maybe_prompt, + toolbar_offset, + } => { + let Some(prompt) = maybe_prompt else { + // Prompt was dismissed, so the dialog should be closed too. + return false; + }; + let mut is_open = true; + + let mut position = prompt.position(); + position.min.y += toolbar_offset.0 as i32; + position.max.y += toolbar_offset.0 as i32; + let area = egui::Area::new(egui::Id::new("select-window")) + .fixed_pos(egui::pos2(position.min.x as f32, position.max.y as f32)); + + let mut selected_option = prompt.selected_option(); + + fn display_option( + ui: &mut egui::Ui, + option: &SelectElementOption, + selected_option: &mut Option<usize>, + is_open: &mut bool, + in_group: bool, + ) { + let is_checked = + selected_option.is_some_and(|selected_index| selected_index == option.id); + + // TODO: Surely there's a better way to align text in a selectable label in egui. + let label_text = if in_group { + format!(" {}", option.label) + } else { + option.label.to_owned() + }; + let label = if option.is_disabled { + egui::RichText::new(&label_text).strikethrough() + } else { + egui::RichText::new(&label_text) + }; + let clickable_area = ui + .allocate_ui_with_layout( + [ui.available_width(), 0.0].into(), + egui::Layout::top_down_justified(egui::Align::LEFT), + |ui| ui.selectable_label(is_checked, label), + ) + .inner; + + if clickable_area.clicked() && !option.is_disabled { + *selected_option = Some(option.id); + *is_open = false; + } + + if clickable_area.hovered() && option.is_disabled { + ui.ctx().set_cursor_icon(egui::CursorIcon::NotAllowed); + } + } + + let modal = Modal::new("select_element_picker".into()).area(area); + modal.show(ctx, |ui| { + for option_or_optgroup in prompt.options() { + match &option_or_optgroup { + SelectElementOptionOrOptgroup::Option(option) => { + display_option( + ui, + option, + &mut selected_option, + &mut is_open, + false, + ); + }, + SelectElementOptionOrOptgroup::Optgroup { label, options } => { + ui.label(egui::RichText::new(label).strong()); + + for option in options { + display_option( + ui, + option, + &mut selected_option, + &mut is_open, + true, + ); + } + }, + } + } + }); + + prompt.select(selected_option); + + if !is_open { + maybe_prompt.take().unwrap().submit(); + } + + is_open + }, } } } |