gardesk/garfield / ba16d6a

Browse files

Open With: add input dialog for custom application

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
ba16d6ac8d7dbe2ed9288b4c9cace26b1fd783d1
Parents
23407d9
Tree
8a140e0

4 changed files

StatusFile+-
M garfield/src/app.rs 87 40
M garfield/src/ui/context_menu.rs 1 2
M garfield/src/ui/dialog.rs 356 0
M garfield/src/ui/mod.rs 1 1
garfield/src/app.rsmodified
@@ -6,7 +6,7 @@ use garfield::core::{
66
     trash_files, restore_from_trash,
77
 };
88
 use garfield::ui::pane::SplitDirection;
9
-use garfield::ui::{AddressBar, Breadcrumb, ConfirmDialog, ConflictAction, ConflictDialog, ContextMenu, ContextMenuAction, ContextType, DialogResult, HelpModal, Pane, ProgressDialog, Sidebar, StatusBar, TabBar, TabInfo, Toolbar, ToolbarAction, ViewMode, TAB_BAR_HEIGHT, TOOLBAR_HEIGHT};
9
+use garfield::ui::{AddressBar, Breadcrumb, ConfirmDialog, ConflictAction, ConflictDialog, ContextMenu, ContextMenuAction, ContextType, DialogResult, HelpModal, InputDialog, InputResult, Pane, ProgressDialog, Sidebar, StatusBar, TabBar, TabInfo, Toolbar, ToolbarAction, ViewMode, TAB_BAR_HEIGHT, TOOLBAR_HEIGHT};
1010
 use anyhow::Result;
1111
 use gartk_core::{InputEvent, Key, MouseButton, Point, Rect, Theme};
1212
 use gartk_render::{Renderer, Surface, TextStyle};
@@ -82,6 +82,10 @@ pub struct App {
8282
     progress_dialog: ProgressDialog,
8383
     /// Context menu for right-click actions.
8484
     context_menu: ContextMenu,
85
+    /// Input dialog for text entry.
86
+    input_dialog: InputDialog,
87
+    /// Path pending "Open With" custom application.
88
+    pending_open_with_path: Option<PathBuf>,
8589
     /// Paths pending delete confirmation.
8690
     pending_delete_paths: Vec<PathBuf>,
8791
     /// Undo/redo stack for file operations.
@@ -201,6 +205,9 @@ impl App {
201205
         // Create context menu (full window bounds for positioning)
202206
         let context_menu = ContextMenu::new(Rect::new(0, 0, width, height));
203207
 
208
+        // Create input dialog (full window bounds)
209
+        let input_dialog = InputDialog::new(Rect::new(0, 0, width, height));
210
+
204211
         // Content area bounds (for panes)
205212
         let content_bounds = Rect::new(
206213
             sidebar_w as i32,
@@ -250,6 +257,8 @@ impl App {
250257
             conflict_dialog,
251258
             progress_dialog,
252259
             context_menu,
260
+            input_dialog,
261
+            pending_open_with_path: None,
253262
             pending_delete_paths: Vec::new(),
254263
             undo_stack: UndoStack::new(),
255264
             pending_paste: None,
@@ -359,6 +368,14 @@ impl App {
359368
             return;
360369
         }
361370
 
371
+        // Check input dialog
372
+        if self.input_dialog.is_visible() {
373
+            if let Some(result) = self.input_dialog.on_click(pos) {
374
+                self.handle_input_result(result);
375
+            }
376
+            return;
377
+        }
378
+
362379
         // Check help modal (clicking outside closes it)
363380
         if self.help_modal.on_click(pos) {
364381
             return;
@@ -612,6 +629,12 @@ impl App {
612629
             return;
613630
         }
614631
 
632
+        // Handle input dialog hover
633
+        if self.input_dialog.is_visible() {
634
+            self.input_dialog.on_mouse_move(pos);
635
+            return;
636
+        }
637
+
615638
         // Handle context menu hover
616639
         if self.context_menu.is_visible() {
617640
             self.context_menu.on_mouse_move(pos);
@@ -710,6 +733,14 @@ impl App {
710733
             return;
711734
         }
712735
 
736
+        // Handle input dialog when visible
737
+        if self.input_dialog.is_visible() {
738
+            if let Some(result) = self.input_dialog.handle_key(key) {
739
+                self.handle_input_result(result);
740
+            }
741
+            return;
742
+        }
743
+
713744
         // Handle help modal when visible
714745
         if self.help_modal.is_visible() {
715746
             match key {
@@ -1516,6 +1547,19 @@ impl App {
15161547
         }
15171548
     }
15181549
 
1550
+    /// Handle input dialog result.
1551
+    fn handle_input_result(&mut self, result: InputResult) {
1552
+        match result {
1553
+            InputResult::Submitted(value) => {
1554
+                // Currently only used for "Open With" custom application
1555
+                self.open_with_custom(&value);
1556
+            }
1557
+            InputResult::Cancelled => {
1558
+                self.pending_open_with_path = None;
1559
+            }
1560
+        }
1561
+    }
1562
+
15191563
     /// Handle conflict dialog result.
15201564
     fn handle_conflict_action(&mut self, action: ConflictAction) {
15211565
         let pending = match self.pending_paste.take() {
@@ -1820,29 +1864,49 @@ impl App {
18201864
 
18211865
     /// Open selected item with a specific application.
18221866
     fn open_with(&mut self, app: &str) {
1823
-        if app.is_empty() {
1824
-            self.status_bar.set_status_message("Application picker not implemented");
1867
+        let paths = self.get_selected_paths();
1868
+        let Some(path) = paths.first().cloned() else {
1869
+            return;
1870
+        };
1871
+
1872
+        // Handle $CUSTOM - show input dialog
1873
+        if app == "$CUSTOM" {
1874
+            self.pending_open_with_path = Some(path);
1875
+            self.input_dialog.show("Open With", "Enter application name:", "");
18251876
             return;
18261877
         }
18271878
 
1828
-        let paths = self.get_selected_paths();
1829
-        if let Some(path) = paths.first() {
1830
-            // Resolve special application identifiers
1831
-            let resolved_app = match app {
1832
-                "$EDITOR" => self.resolve_text_editor(),
1833
-                "$FILEMANAGER" => self.resolve_file_manager(),
1834
-                other => Some(other.to_string()),
1835
-            };
1879
+        // Resolve special application identifiers
1880
+        let resolved_app = match app {
1881
+            "$EDITOR" => self.resolve_text_editor(),
1882
+            other => Some(other.to_string()),
1883
+        };
18361884
 
1837
-            let Some(app_cmd) = resolved_app else {
1838
-                self.status_bar.set_status_message("No suitable application found");
1839
-                return;
1840
-            };
1885
+        let Some(app_cmd) = resolved_app else {
1886
+            self.status_bar.set_status_message("No suitable application found");
1887
+            return;
1888
+        };
18411889
 
1842
-            match std::process::Command::new(&app_cmd).arg(path).spawn() {
1843
-                Ok(_) => self.status_bar.set_status_message(format!("Opened with {}", app_cmd)),
1844
-                Err(e) => self.status_bar.set_status_message(format!("Failed to open with {}: {}", app_cmd, e)),
1845
-            }
1890
+        match std::process::Command::new(&app_cmd).arg(&path).spawn() {
1891
+            Ok(_) => self.status_bar.set_status_message(format!("Opened with {}", app_cmd)),
1892
+            Err(e) => self.status_bar.set_status_message(format!("Failed to open with {}: {}", app_cmd, e)),
1893
+        }
1894
+    }
1895
+
1896
+    /// Open file with custom application (from input dialog).
1897
+    fn open_with_custom(&mut self, app_name: &str) {
1898
+        let Some(path) = self.pending_open_with_path.take() else {
1899
+            return;
1900
+        };
1901
+
1902
+        if app_name.is_empty() {
1903
+            self.status_bar.set_status_message("No application specified");
1904
+            return;
1905
+        }
1906
+
1907
+        match std::process::Command::new(app_name).arg(&path).spawn() {
1908
+            Ok(_) => self.status_bar.set_status_message(format!("Opened with {}", app_name)),
1909
+            Err(e) => self.status_bar.set_status_message(format!("Failed to open with {}: {}", app_name, e)),
18461910
         }
18471911
     }
18481912
 
@@ -1872,27 +1936,6 @@ impl App {
18721936
         Some("xdg-open".to_string())
18731937
     }
18741938
 
1875
-    /// Resolve file manager from environment or common file managers.
1876
-    fn resolve_file_manager(&self) -> Option<String> {
1877
-        // Check XDG default
1878
-        if let Ok(fm) = std::env::var("FILE_MANAGER") {
1879
-            if !fm.is_empty() && self.command_exists(&fm) {
1880
-                return Some(fm);
1881
-            }
1882
-        }
1883
-
1884
-        // Try common file managers (excluding garfield to avoid recursion)
1885
-        let managers = ["nautilus", "dolphin", "thunar", "pcmanfm", "nemo", "caja"];
1886
-        for manager in managers {
1887
-            if self.command_exists(manager) {
1888
-                return Some(manager.to_string());
1889
-            }
1890
-        }
1891
-
1892
-        // Fallback to xdg-open
1893
-        Some("xdg-open".to_string())
1894
-    }
1895
-
18961939
     /// Check if a command exists in PATH.
18971940
     fn command_exists(&self, cmd: &str) -> bool {
18981941
         // Extract just the command name (in case it's a full path or has args)
@@ -2437,6 +2480,7 @@ impl App {
24372480
         self.conflict_dialog.set_bounds(Rect::new(0, 0, width, height));
24382481
         self.progress_dialog.set_bounds(Rect::new(0, 0, width, height));
24392482
         self.context_menu.set_bounds(Rect::new(0, 0, width, height));
2483
+        self.input_dialog.set_bounds(Rect::new(0, 0, width, height));
24402484
     }
24412485
 
24422486
     /// Render the application.
@@ -2506,6 +2550,9 @@ impl App {
25062550
         // Draw conflict dialog overlay (on top of everything)
25072551
         self.conflict_dialog.render(&self.renderer)?;
25082552
 
2553
+        // Draw input dialog overlay (on top of everything)
2554
+        self.input_dialog.render(&self.renderer)?;
2555
+
25092556
         // Draw context menu overlay
25102557
         self.context_menu.render(&self.renderer)?;
25112558
 
garfield/src/ui/context_menu.rsmodified
@@ -378,9 +378,8 @@ impl ContextMenu {
378378
         vec![
379379
             MenuItem::action("Default Application", ContextMenuAction::OpenWith("xdg-open".to_string())),
380380
             MenuItem::action("Text Editor", ContextMenuAction::OpenWith("$EDITOR".to_string())),
381
-            MenuItem::action("File Manager", ContextMenuAction::OpenWith("$FILEMANAGER".to_string())),
382381
             MenuItem::separator(),
383
-            MenuItem::action("Other Application...", ContextMenuAction::OpenWith(String::new())),
382
+            MenuItem::action("Other Application...", ContextMenuAction::OpenWith("$CUSTOM".to_string())),
384383
         ]
385384
     }
386385
 
garfield/src/ui/dialog.rsmodified
@@ -913,3 +913,359 @@ impl ConflictDialog {
913913
         Ok(())
914914
     }
915915
 }
916
+
917
+/// Result of an input dialog.
918
+#[derive(Debug, Clone)]
919
+pub enum InputResult {
920
+    /// User submitted the input.
921
+    Submitted(String),
922
+    /// User cancelled.
923
+    Cancelled,
924
+}
925
+
926
+/// A modal dialog for text input.
927
+pub struct InputDialog {
928
+    /// Window bounds (for centering).
929
+    bounds: Rect,
930
+    /// Dialog title.
931
+    title: String,
932
+    /// Dialog prompt/label.
933
+    prompt: String,
934
+    /// Current input value.
935
+    input: String,
936
+    /// Cursor position in input.
937
+    cursor: usize,
938
+    /// Whether the dialog is visible.
939
+    visible: bool,
940
+    /// Currently focused element (0 = input, 1 = ok, 2 = cancel).
941
+    focused: usize,
942
+    /// Hovered button.
943
+    hovered_button: Option<usize>,
944
+}
945
+
946
+impl InputDialog {
947
+    /// Create a new input dialog.
948
+    pub fn new(bounds: Rect) -> Self {
949
+        Self {
950
+            bounds,
951
+            title: String::new(),
952
+            prompt: String::new(),
953
+            input: String::new(),
954
+            cursor: 0,
955
+            visible: false,
956
+            focused: 0,
957
+            hovered_button: None,
958
+        }
959
+    }
960
+
961
+    /// Set bounds.
962
+    pub fn set_bounds(&mut self, bounds: Rect) {
963
+        self.bounds = bounds;
964
+    }
965
+
966
+    /// Show the dialog.
967
+    pub fn show(&mut self, title: &str, prompt: &str, initial_value: &str) {
968
+        self.title = title.to_string();
969
+        self.prompt = prompt.to_string();
970
+        self.input = initial_value.to_string();
971
+        self.cursor = self.input.len();
972
+        self.visible = true;
973
+        self.focused = 0;
974
+        self.hovered_button = None;
975
+    }
976
+
977
+    /// Check if visible.
978
+    pub fn is_visible(&self) -> bool {
979
+        self.visible
980
+    }
981
+
982
+    /// Hide the dialog.
983
+    pub fn hide(&mut self) {
984
+        self.visible = false;
985
+    }
986
+
987
+    /// Handle key press. Returns Some(result) if dialog should close.
988
+    pub fn handle_key(&mut self, key: &Key) -> Option<InputResult> {
989
+        if !self.visible {
990
+            return None;
991
+        }
992
+
993
+        match key {
994
+            Key::Escape => {
995
+                self.hide();
996
+                Some(InputResult::Cancelled)
997
+            }
998
+            Key::Return => {
999
+                if self.focused == 0 || self.focused == 1 {
1000
+                    let result = self.input.clone();
1001
+                    self.hide();
1002
+                    Some(InputResult::Submitted(result))
1003
+                } else {
1004
+                    self.hide();
1005
+                    Some(InputResult::Cancelled)
1006
+                }
1007
+            }
1008
+            Key::Tab => {
1009
+                self.focused = (self.focused + 1) % 3;
1010
+                None
1011
+            }
1012
+            Key::Char(c) if self.focused == 0 => {
1013
+                self.input.insert(self.cursor, *c);
1014
+                self.cursor += 1;
1015
+                None
1016
+            }
1017
+            Key::Backspace if self.focused == 0 => {
1018
+                if self.cursor > 0 {
1019
+                    self.cursor -= 1;
1020
+                    self.input.remove(self.cursor);
1021
+                }
1022
+                None
1023
+            }
1024
+            Key::Delete if self.focused == 0 => {
1025
+                if self.cursor < self.input.len() {
1026
+                    self.input.remove(self.cursor);
1027
+                }
1028
+                None
1029
+            }
1030
+            Key::Left if self.focused == 0 => {
1031
+                if self.cursor > 0 {
1032
+                    self.cursor -= 1;
1033
+                }
1034
+                None
1035
+            }
1036
+            Key::Right if self.focused == 0 => {
1037
+                if self.cursor < self.input.len() {
1038
+                    self.cursor += 1;
1039
+                }
1040
+                None
1041
+            }
1042
+            Key::Home if self.focused == 0 => {
1043
+                self.cursor = 0;
1044
+                None
1045
+            }
1046
+            Key::End if self.focused == 0 => {
1047
+                self.cursor = self.input.len();
1048
+                None
1049
+            }
1050
+            _ => None,
1051
+        }
1052
+    }
1053
+
1054
+    /// Handle mouse click. Returns Some(result) if dialog should close.
1055
+    pub fn on_click(&mut self, pos: Point) -> Option<InputResult> {
1056
+        if !self.visible {
1057
+            return None;
1058
+        }
1059
+
1060
+        let (ok_rect, cancel_rect) = self.button_rects();
1061
+        let input_rect = self.input_rect();
1062
+
1063
+        if input_rect.contains_point(pos) {
1064
+            self.focused = 0;
1065
+            return None;
1066
+        }
1067
+
1068
+        if ok_rect.contains_point(pos) {
1069
+            let result = self.input.clone();
1070
+            self.hide();
1071
+            return Some(InputResult::Submitted(result));
1072
+        }
1073
+
1074
+        if cancel_rect.contains_point(pos) {
1075
+            self.hide();
1076
+            return Some(InputResult::Cancelled);
1077
+        }
1078
+
1079
+        None
1080
+    }
1081
+
1082
+    /// Handle mouse move.
1083
+    pub fn on_mouse_move(&mut self, pos: Point) {
1084
+        if !self.visible {
1085
+            return;
1086
+        }
1087
+
1088
+        let (ok_rect, cancel_rect) = self.button_rects();
1089
+
1090
+        if ok_rect.contains_point(pos) {
1091
+            self.hovered_button = Some(1);
1092
+        } else if cancel_rect.contains_point(pos) {
1093
+            self.hovered_button = Some(2);
1094
+        } else {
1095
+            self.hovered_button = None;
1096
+        }
1097
+    }
1098
+
1099
+    /// Get the dialog rectangle.
1100
+    fn dialog_rect(&self) -> Rect {
1101
+        let dialog_width = 400.min(self.bounds.width.saturating_sub(40));
1102
+        let dialog_height = 160.min(self.bounds.height.saturating_sub(40));
1103
+        let x = self.bounds.x + (self.bounds.width as i32 - dialog_width as i32) / 2;
1104
+        let y = self.bounds.y + (self.bounds.height as i32 - dialog_height as i32) / 2;
1105
+        Rect::new(x, y, dialog_width, dialog_height)
1106
+    }
1107
+
1108
+    /// Get the input field rectangle.
1109
+    fn input_rect(&self) -> Rect {
1110
+        let dialog = self.dialog_rect();
1111
+        let input_width = dialog.width.saturating_sub(40);
1112
+        let input_height = 32;
1113
+        let x = dialog.x + 20;
1114
+        let y = dialog.y + 70;
1115
+        Rect::new(x, y, input_width, input_height)
1116
+    }
1117
+
1118
+    /// Get button rectangles (ok, cancel).
1119
+    fn button_rects(&self) -> (Rect, Rect) {
1120
+        let dialog = self.dialog_rect();
1121
+        let button_width = 80;
1122
+        let button_height = 28;
1123
+        let button_y = dialog.y + dialog.height as i32 - button_height as i32 - 16;
1124
+        let button_gap = 16;
1125
+        let total_width = button_width * 2 + button_gap;
1126
+        let start_x = dialog.x + (dialog.width as i32 - total_width as i32) / 2;
1127
+
1128
+        let ok_rect = Rect::new(start_x, button_y, button_width, button_height);
1129
+        let cancel_rect = Rect::new(start_x + button_width as i32 + button_gap as i32, button_y, button_width, button_height);
1130
+
1131
+        (ok_rect, cancel_rect)
1132
+    }
1133
+
1134
+    /// Render the dialog.
1135
+    pub fn render(&self, renderer: &Renderer) -> Result<()> {
1136
+        if !self.visible {
1137
+            return Ok(());
1138
+        }
1139
+
1140
+        let theme = renderer.theme();
1141
+
1142
+        // Dim background overlay
1143
+        renderer.fill_rect(self.bounds, gartk_core::Color::from_u8(0, 0, 0, 180))?;
1144
+
1145
+        let dialog_rect = self.dialog_rect();
1146
+
1147
+        // Dialog background
1148
+        renderer.fill_rounded_rect(dialog_rect, 8.0, theme.background)?;
1149
+        renderer.stroke_rounded_rect(dialog_rect, 8.0, theme.border, 1.0)?;
1150
+
1151
+        // Title
1152
+        let title_style = TextStyle::new()
1153
+            .font_family(&theme.font_family)
1154
+            .font_size(theme.font_size + 2.0)
1155
+            .color(theme.foreground);
1156
+
1157
+        renderer.text(
1158
+            &self.title,
1159
+            (dialog_rect.x + 20) as f64,
1160
+            (dialog_rect.y + 20) as f64,
1161
+            &title_style,
1162
+        )?;
1163
+
1164
+        // Prompt
1165
+        let prompt_style = TextStyle::new()
1166
+            .font_family(&theme.font_family)
1167
+            .font_size(theme.font_size)
1168
+            .color(theme.item_foreground);
1169
+
1170
+        renderer.text(
1171
+            &self.prompt,
1172
+            (dialog_rect.x + 20) as f64,
1173
+            (dialog_rect.y + 48) as f64,
1174
+            &prompt_style,
1175
+        )?;
1176
+
1177
+        // Input field
1178
+        let input_rect = self.input_rect();
1179
+        let input_focused = self.focused == 0;
1180
+
1181
+        renderer.fill_rounded_rect(input_rect, 4.0, theme.item_background)?;
1182
+        if input_focused {
1183
+            renderer.stroke_rounded_rect(input_rect, 4.0, theme.selection_background, 2.0)?;
1184
+        } else {
1185
+            renderer.stroke_rounded_rect(input_rect, 4.0, theme.border, 1.0)?;
1186
+        }
1187
+
1188
+        // Input text
1189
+        let input_style = TextStyle::new()
1190
+            .font_family(&theme.font_family)
1191
+            .font_size(theme.font_size)
1192
+            .color(theme.foreground);
1193
+
1194
+        let text_y = input_rect.y + (input_rect.height as i32 - theme.font_size as i32) / 2;
1195
+        renderer.text(
1196
+            &self.input,
1197
+            (input_rect.x + 8) as f64,
1198
+            text_y as f64,
1199
+            &input_style,
1200
+        )?;
1201
+
1202
+        // Cursor (if input is focused)
1203
+        if input_focused {
1204
+            let cursor_text = &self.input[..self.cursor];
1205
+            let cursor_x = if cursor_text.is_empty() {
1206
+                input_rect.x + 8
1207
+            } else {
1208
+                let width = renderer.measure_text(cursor_text, &input_style)
1209
+                    .map(|m| m.width as i32)
1210
+                    .unwrap_or(0);
1211
+                input_rect.x + 8 + width
1212
+            };
1213
+
1214
+            renderer.line(
1215
+                cursor_x as f64,
1216
+                (input_rect.y + 6) as f64,
1217
+                cursor_x as f64,
1218
+                (input_rect.y + input_rect.height as i32 - 6) as f64,
1219
+                theme.foreground,
1220
+                1.0,
1221
+            )?;
1222
+        }
1223
+
1224
+        // Buttons
1225
+        let (ok_rect, cancel_rect) = self.button_rects();
1226
+
1227
+        let button_style = TextStyle::new()
1228
+            .font_family(&theme.font_family)
1229
+            .font_size(theme.font_size)
1230
+            .color(theme.foreground);
1231
+
1232
+        // OK button
1233
+        let ok_focused = self.focused == 1;
1234
+        let ok_hovered = self.hovered_button == Some(1);
1235
+        let ok_bg = if ok_focused || ok_hovered {
1236
+            theme.item_hover_background
1237
+        } else {
1238
+            theme.item_background
1239
+        };
1240
+        renderer.fill_rounded_rect(ok_rect, 4.0, ok_bg)?;
1241
+        if ok_focused {
1242
+            renderer.stroke_rounded_rect(ok_rect, 4.0, theme.foreground, 2.0)?;
1243
+        }
1244
+
1245
+        let ok_text = "OK";
1246
+        let ok_width = renderer.measure_text(ok_text, &button_style)?.width;
1247
+        let ok_x = ok_rect.x + (ok_rect.width as i32 - ok_width as i32) / 2;
1248
+        let button_text_y = ok_rect.y + (ok_rect.height as i32 - theme.font_size as i32) / 2;
1249
+        renderer.text(ok_text, ok_x as f64, button_text_y as f64, &button_style)?;
1250
+
1251
+        // Cancel button
1252
+        let cancel_focused = self.focused == 2;
1253
+        let cancel_hovered = self.hovered_button == Some(2);
1254
+        let cancel_bg = if cancel_focused || cancel_hovered {
1255
+            theme.item_hover_background
1256
+        } else {
1257
+            theme.item_background
1258
+        };
1259
+        renderer.fill_rounded_rect(cancel_rect, 4.0, cancel_bg)?;
1260
+        if cancel_focused {
1261
+            renderer.stroke_rounded_rect(cancel_rect, 4.0, theme.foreground, 2.0)?;
1262
+        }
1263
+
1264
+        let cancel_text = "Cancel";
1265
+        let cancel_width = renderer.measure_text(cancel_text, &button_style)?.width;
1266
+        let cancel_x = cancel_rect.x + (cancel_rect.width as i32 - cancel_width as i32) / 2;
1267
+        renderer.text(cancel_text, cancel_x as f64, button_text_y as f64, &button_style)?;
1268
+
1269
+        Ok(())
1270
+    }
1271
+}
garfield/src/ui/mod.rsmodified
@@ -17,7 +17,7 @@ pub mod toolbar;
1717
 
1818
 pub use address_bar::AddressBar;
1919
 pub use context_menu::{ContextMenu, ContextMenuAction, ContextType};
20
-pub use dialog::{ConfirmDialog, ConflictAction, ConflictDialog, DialogResult, ProgressDialog, ProgressInfo};
20
+pub use dialog::{ConfirmDialog, ConflictAction, ConflictDialog, DialogResult, InputDialog, InputResult, ProgressDialog, ProgressInfo};
2121
 pub use breadcrumb::Breadcrumb;
2222
 pub use column_view::{ColumnClickResult, ColumnView};
2323
 pub use grid_view::GridView;