aboutsummaryrefslogtreecommitdiffstats
path: root/ports/servoshell/desktop/dialog.rs
diff options
context:
space:
mode:
authorSimon Wülker <simon.wuelker@arcor.de>2025-04-03 14:11:55 +0200
committerGitHub <noreply@github.com>2025-04-03 12:11:55 +0000
commit0e99539dab4c059ba3c3750cfda42e246ce8b4f0 (patch)
treea5703fc0ca14d1bc66345b3168d183bab037427a /ports/servoshell/desktop/dialog.rs
parent6e9d01b908b51d3f44f858476acf3af8c1d44289 (diff)
downloadservo-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.rs114
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
+ },
}
}
}