diff --git a/src/Library/demos/Account/main.vala b/src/Library/demos/Account/main.vala new file mode 100644 index 000000000..b1e1cfcf8 --- /dev/null +++ b/src/Library/demos/Account/main.vala @@ -0,0 +1,54 @@ +#!/usr/bin/env -S vala workbench.vala --pkg libadwaita-1 --pkg libportal-gtk4 + +private Gtk.Revealer revealer; +private Adw.EntryRow entry; +private Adw.Avatar avatar; +private Gtk.Label username; +private Gtk.Label display; +private Xdp.Portal portal; +private Xdp.Parent parent; + +public void main () { + portal = new Xdp.Portal (); + parent = Xdp.parent_new_gtk (workbench.window); + + revealer = (Gtk.Revealer) workbench.builder.get_object ("revealer"); + entry = (Adw.EntryRow) workbench.builder.get_object ("entry"); + avatar = (Adw.Avatar) workbench.builder.get_object ("avatar"); + username = (Gtk.Label) workbench.builder.get_object ("username"); + display = (Gtk.Label) workbench.builder.get_object ("name"); + + var button = (Gtk.Button) workbench.builder.get_object ("button"); + button.clicked.connect (on_button_clicked); +} + +private async void on_button_clicked () { + try { + string reason = entry.text; + Variant result = yield portal.get_user_information (parent, reason, NONE, null); + + /* + * result is a Variant dictionary containing the following fields: + * id (s): the user id + * name (s): the users real name + * image (s): the uri of an image file for the users avatar picture + */ + + var id = (string) result.lookup_value ("id", VariantType.STRING); + var name = (string) result.lookup_value ("name", VariantType.STRING); + var uri = (string) result.lookup_value ("image", VariantType.STRING); + + var file = File.new_for_uri (uri); + var texture = Gdk.Texture.from_file (file); + + username.label = id; + display.label = name; + avatar.custom_image = texture; + revealer.reveal_child = true; + + entry.text = ""; + message ("Information Retrieved"); + } catch (Error e) { + critical (e.message); + } +} diff --git a/src/Library/demos/Color Picker/main.vala b/src/Library/demos/Color Picker/main.vala new file mode 100644 index 000000000..7663a5174 --- /dev/null +++ b/src/Library/demos/Color Picker/main.vala @@ -0,0 +1,36 @@ +#!/usr/bin/env -S vala workbench.vala --pkg libadwaita-1 --pkg libportal-gtk4 + +private Xdp.Portal portal; +private Xdp.Parent parent; + +public void main () { + portal = new Xdp.Portal (); + parent = Xdp.parent_new_gtk (workbench.window); + + var button = (Gtk.Button) workbench.builder.get_object ("button"); + button.clicked.connect (on_button_clicked); +} + +private async void on_button_clicked () { + try { + // result is a variant of the form (ddd), containing red green and blue components in the range [0,1] + Variant result = yield portal.pick_color (parent, null); + + double r, g, b; + VariantIter iter = result.iterator (); // Iterate over the array in the variant + iter.next ("d", out r); + iter.next ("d", out g); + iter.next ("d", out b); + + var color = Gdk.RGBA () { + red = (float) r, + green = (float) g, + blue = (float) b, + alpha = 1.0f + }; + + message (@"Selected color is $color"); + } catch (Error e) { + critical (e.message); + } +} diff --git a/src/Library/demos/Email/main.vala b/src/Library/demos/Email/main.vala new file mode 100644 index 000000000..a4624c526 --- /dev/null +++ b/src/Library/demos/Email/main.vala @@ -0,0 +1,40 @@ +#!/usr/bin/env -S vala workbench.vala --pkg libadwaita-1 --pkg libportal-gtk4 + +private Xdp.Portal portal; +private Xdp.Parent parent; +private Gtk.Entry entry; + +public void main () { + portal = new Xdp.Portal (); + parent = Xdp.parent_new_gtk (workbench.window); + + entry = (Gtk.Entry) workbench.builder.get_object ("entry"); + var button = (Gtk.Button) workbench.builder.get_object ("button"); + button.clicked.connect (on_button_clicked); +} + +private async void on_button_clicked () { + string email_address = entry.text; + + try { + bool success = yield portal.compose_email ( + parent, + { email_address }, // addresses + null, // cc + null, // bcc + "Email from Workbench", // subject + "Hello World!", // body + null, + NONE, + null + ); + + if (success) { + message ("Success"); + return; + } + message ("Failure: verify that you have an email application."); + } catch (Error e) { + critical (e.message); + } +} diff --git a/src/Library/demos/Event Controllers/main.vala b/src/Library/demos/Event Controllers/main.vala new file mode 100644 index 000000000..e2328c497 --- /dev/null +++ b/src/Library/demos/Event Controllers/main.vala @@ -0,0 +1,90 @@ +#!/usr/bin/env -S vala workbench.vala --pkg libadwaita-1 + +private bool ctrl_pressed = false; + +public void main () { + Gtk.Window window = workbench.window; + + var pic1 = (Gtk.Picture) workbench.builder.get_object ("pic1"); + var pic2 = (Gtk.Picture) workbench.builder.get_object ("pic2"); + + pic1.file = File.new_for_uri (workbench.resolve ("image1.png")); + pic2.file = File.new_for_uri (workbench.resolve ("image2.png")); + + var stack = (Gtk.Stack) workbench.builder.get_object ("stack"); + var primary_button = (Gtk.Button) workbench.builder.get_object ("primary_button"); + var middle_button = (Gtk.Button) workbench.builder.get_object ("middle_button"); + var secondary_button = (Gtk.Button) workbench.builder.get_object ("secondary_button"); + var ctrl_button = (Gtk.Button) workbench.builder.get_object ("ctrl_button"); + + var key_controller = new Gtk.EventControllerKey (); + // Gtk.Window hides Gtk.Widget's add_controller method, thus we need to cast it + ((Gtk.Widget) window).add_controller (key_controller); + + key_controller.key_pressed.connect ((keyval, keycode, state) => { + if (keyval == Gdk.Key.Control_L || keyval == Gdk.Key.Control_R) { + ctrl_pressed = true; + } + return true; + }); + + key_controller.key_released.connect ((keyval, keycode, state) => { + if (keyval == Gdk.Key.Control_L || keyval == Gdk.Key.Control_R) { + ctrl_pressed = false; + } + }); + + ctrl_button.clicked.connect (() => { + if (ctrl_pressed) { + ctrl_button.label = "Click to Deactivate"; + ctrl_button.add_css_class ("suggested-action"); + } else { + ctrl_button.label = "Ctrl + Click to Activate"; + ctrl_button.remove_css_class ("suggested-action"); + } + }); + + var gesture_click = new Gtk.GestureClick () { + button = 0 + }; + ((Gtk.Widget) window).add_controller (gesture_click); + + gesture_click.pressed.connect ((gesture, n_press, x, y) => { + switch (gesture.get_current_button ()) { + case Gdk.BUTTON_PRIMARY: + primary_button.add_css_class ("suggested-action"); + break; + case Gdk.BUTTON_MIDDLE: + middle_button.add_css_class ("suggested-action"); + break; + case Gdk.BUTTON_SECONDARY: + secondary_button.add_css_class ("suggested-action"); + break; + } + }); + + gesture_click.released.connect ((gesture, n_press, x, y) => { + switch (gesture.get_current_button ()) { + case Gdk.BUTTON_PRIMARY: + primary_button.remove_css_class ("suggested-action"); + break; + case Gdk.BUTTON_MIDDLE: + middle_button.remove_css_class ("suggested-action"); + break; + case Gdk.BUTTON_SECONDARY: + secondary_button.remove_css_class ("suggested-action"); + break; + } + }); + + var gesture_swipe = new Gtk.GestureSwipe (); + stack.add_controller (gesture_swipe); + + gesture_swipe.swipe.connect ((vel_x, vel_y) => { + if (vel_x > 0) { + stack.visible_child_name = "pic1"; + } else { + stack.visible_child_name = "pic2"; + } + }); +} diff --git a/src/Library/demos/Location/main.vala b/src/Library/demos/Location/main.vala new file mode 100644 index 000000000..34a0ebe32 --- /dev/null +++ b/src/Library/demos/Location/main.vala @@ -0,0 +1,125 @@ +#!/usr/bin/env -S vala workbench.vala --pkg libadwaita-1 --pkg libportal-gtk4 + +private Xdp.Portal portal; +private Xdp.Parent parent; + +private Gtk.Revealer revealer; +private Gtk.Button start_button; +private Gtk.Button close_button; +private Gtk.SpinButton distance_threshold; +private Gtk.SpinButton time_threshold; +private Adw.ComboRow accuracy_button; + +private Gtk.Label latitude_label; +private Gtk.Label longitude_label; +private Gtk.Label accuracy_label; +private Gtk.Label altitude_label; +private Gtk.Label speed_label; +private Gtk.Label heading_label; +private Gtk.Label description_label; +private Gtk.Label timestamp_label; + +public void main () { + portal = new Xdp.Portal (); + parent = Xdp.parent_new_gtk (workbench.window); + + revealer = (Gtk.Revealer) workbench.builder.get_object ("revealer"); + start_button = (Gtk.Button) workbench.builder.get_object ("start"); + close_button = (Gtk.Button) workbench.builder.get_object ("close"); + distance_threshold = (Gtk.SpinButton) workbench.builder.get_object ("distance_threshold"); + time_threshold = (Gtk.SpinButton) workbench.builder.get_object ("time_threshold"); + accuracy_button = (Adw.ComboRow) workbench.builder.get_object ("accuracy_button"); + + latitude_label = (Gtk.Label) workbench.builder.get_object ("latitude"); + longitude_label = (Gtk.Label) workbench.builder.get_object ("longitude"); + accuracy_label = (Gtk.Label) workbench.builder.get_object ("accuracy"); + altitude_label = (Gtk.Label) workbench.builder.get_object ("altitude"); + speed_label = (Gtk.Label) workbench.builder.get_object ("speed"); + heading_label = (Gtk.Label) workbench.builder.get_object ("heading"); + description_label = (Gtk.Label) workbench.builder.get_object ("description"); + timestamp_label = (Gtk.Label) workbench.builder.get_object ("timestamp"); + + start_button.clicked.connect (start_session); + close_button.clicked.connect (close_session); + + time_threshold.value_changed.connect (() => { + message ("Time threshold changed"); + restart_session (); + }); + + distance_threshold.value_changed.connect (() => { + message ("Distance threshold changed"); + restart_session (); + }); + + accuracy_button.notify["selected-item"].connect (() => { + message ("Accuracy changed"); + restart_session (); + }); + + portal.location_updated.connect (on_location_updated); +} + +private void on_location_updated ( + double latitude, + double longitude, + double altitude, + double accuracy, + double speed, + double heading, + string description, + int64 timestamp_seconds, + int64 timestamp_ms +) { + message ("Location updated"); + latitude_label.label = latitude.to_string (); + longitude_label.label = longitude.to_string (); + accuracy_label.label = accuracy.to_string (); + altitude_label.label = altitude.to_string (); + speed_label.label = speed.to_string (); + heading_label.label = heading.to_string (); + description_label.label = description; + + // Convert UNIX timestamp to local date and time string + var timestamp = new DateTime.from_unix_local (timestamp_ms); + timestamp_label.label = timestamp.to_string (); +} + +private void restart_session () { + portal.location_monitor_stop (); + revealer.reveal_child = false; + start_session.begin (); +} + +private async void start_session () { + start_button.sensitive = false; + close_button.sensitive = true; + + try { + bool result = yield portal.location_monitor_start ( + parent, + (uint) distance_threshold.value, + (uint) time_threshold.value, + (Xdp.LocationAccuracy) accuracy_button.selected, + NONE, + null + ); + + if (result) { + message ("Location access granted"); + revealer.reveal_child = true; + return; + } + message ("Error retrieving location"); + } catch (Error e) { + critical (e.message); + } +} + +private void close_session () { + start_button.sensitive = false; + close_button.sensitive = false; + portal.location_monitor_stop (); + revealer.reveal_child = false; + message ("Session Closed"); +} diff --git a/src/Library/demos/Navigation Split View/main.blp b/src/Library/demos/Navigation Split View/main.blp new file mode 100644 index 000000000..3c307ffd9 --- /dev/null +++ b/src/Library/demos/Navigation Split View/main.blp @@ -0,0 +1,66 @@ +using Gtk 4.0; +using Adw 1; + +Adw.Window { + width-request: 360; + height-request: 200; + default-width: 640; + default-height: 480; + + Adw.Breakpoint { + condition ("max-width: 400sp") + setters { + split_view.collapsed: true; + button.visible: true; + } + } + + content: Adw.NavigationSplitView split_view { + sidebar: Adw.NavigationPage { + title: "Sidebar"; + tag: "sidebar"; + child: Adw.ToolbarView { + + [top] + Adw.HeaderBar { + show-title: false; + } + + content: Adw.StatusPage { + title: _("Sidebar"); + + Button button { + visible: false; + halign: center; + can-shrink: true; + label: _("Open Content"); + action-name: "navigation.push"; + action-target: "'content'"; + styles ["pill"] + } + }; + }; + }; + + content: Adw.NavigationPage { + title: "Content"; + tag: "content"; + child: Adw.ToolbarView { + + [top] + Adw.HeaderBar { + show-title: false; + } + + content: Adw.StatusPage { + title: _("Content"); + + LinkButton { + label: "API Reference"; + uri: "https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.NavigationSplitView.html"; + } + }; + }; + }; + }; +} diff --git a/src/Library/demos/Navigation Split View/main.json b/src/Library/demos/Navigation Split View/main.json new file mode 100644 index 000000000..30555a9a0 --- /dev/null +++ b/src/Library/demos/Navigation Split View/main.json @@ -0,0 +1,6 @@ +{ + "category": "navigation", + "description": "A widget presenting sidebar and content side by side or as a navigation view.", + "panels": ["ui", "preview"], + "autorun": true +} diff --git a/src/Library/demos/Overlay Split View/main.blp b/src/Library/demos/Overlay Split View/main.blp new file mode 100644 index 000000000..1e6502b5d --- /dev/null +++ b/src/Library/demos/Overlay Split View/main.blp @@ -0,0 +1,85 @@ +using Gtk 4.0; +using Adw 1; + +Adw.Window { + width-request: 360; + height-request: 200; + default-width: 640; + default-height: 480; + title: _("Overlay Split view"); + + Adw.Breakpoint { + condition ("max-width: 400sp") + setters { + split_view.collapsed: true; + } + } + + content: Adw.ToolbarView { + top-bar-style: raised; + + [top] + Adw.HeaderBar { + + [start] + ToggleButton show_sidebar_button { + icon-name: "dock-left"; + tooltip-text: _("Toggle Sidebar"); + active: bind split_view.show-sidebar; + visible: bind start_toggle.active; + } + + [end] + ToggleButton { + icon-name: "dock-right"; + tooltip-text: _("Toggle Sidebar"); + active: bind show_sidebar_button.active; + visible: bind end_toggle.active; + } + } + + content: Adw.OverlaySplitView split_view { + show-sidebar: bind show_sidebar_button.active; + + sidebar: Adw.StatusPage { + title: _("Sidebar"); + + child: Box { + orientation: vertical; + halign: center; + spacing: 18; + + ToggleButton start_toggle { + label: _("Start"); + can-shrink: true; + active: true; + styles ["pill"] + } + + ToggleButton end_toggle { + label: _("End"); + can-shrink: true; + group: start_toggle; + styles ["pill"] + } + }; + }; + + content: Adw.StatusPage { + title: _("Content"); + + Box { + orientation: vertical; + valign: center; + spacing: 18; + + LinkButton { + label: "API Reference"; + uri: "https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.OverlaySplitView.html"; + margin-top: 24; + } + } + }; + }; + }; +} diff --git a/src/Library/demos/Overlay Split View/main.js b/src/Library/demos/Overlay Split View/main.js new file mode 100644 index 000000000..44ae37e2c --- /dev/null +++ b/src/Library/demos/Overlay Split View/main.js @@ -0,0 +1,13 @@ +import Gtk from "gi://Gtk"; + +const split_view = workbench.builder.get_object("split_view"); +const start_toggle = workbench.builder.get_object("start_toggle"); +const end_toggle = workbench.builder.get_object("end_toggle"); + +start_toggle.connect("toggled", () => { + split_view.sidebar_position = Gtk.PackType.START; +}); + +end_toggle.connect("toggled", () => { + split_view.sidebar_position = Gtk.PackType.END; +}); diff --git a/src/Library/demos/Overlay Split View/main.json b/src/Library/demos/Overlay Split View/main.json new file mode 100644 index 000000000..3e9dee673 --- /dev/null +++ b/src/Library/demos/Overlay Split View/main.json @@ -0,0 +1,7 @@ +{ + "name": "Overlay Split View", + "category": "navigation", + "description": "A content and sidebar view", + "panels": ["ui", "preview"], + "autorun": true +} diff --git a/src/Library/demos/Screenshot/main.vala b/src/Library/demos/Screenshot/main.vala new file mode 100644 index 000000000..6cb7d6c4e --- /dev/null +++ b/src/Library/demos/Screenshot/main.vala @@ -0,0 +1,37 @@ +#!/usr/bin/env -S vala workbench.vala --pkg libadwaita-1 --pkg libportal-gtk4 + +private Xdp.Portal portal; +private Xdp.Parent parent; +private Gtk.Picture picture; + +public void main () { + portal = new Xdp.Portal (); + parent = Xdp.parent_new_gtk (workbench.window); + picture = (Gtk.Picture) workbench.builder.get_object ("picture"); + + var button = (Gtk.Button) workbench.builder.get_object ("button"); + button.clicked.connect (on_button_clicked); +} + +private async void on_button_clicked () { + try { + string uri = yield portal.take_screenshot (parent, NONE, null); + picture.file = File.new_for_uri (uri); + } catch (Error e) { + show_permission_error (); + } +} + +private void show_permission_error () { + var dialog = new Adw.MessageDialog ( + workbench.window, + "Permission Error", + "Ensure Screenshot permission is enabled in\nSettings → Apps → Workbench" + ) { + close_response = "ok", + modal = true, + }; + + dialog.add_response ("ok", "OK"); + dialog.present (); +} diff --git a/src/Library/demos/Scrolled Window/main.blp b/src/Library/demos/Scrolled Window/main.blp new file mode 100644 index 000000000..57f16b258 --- /dev/null +++ b/src/Library/demos/Scrolled Window/main.blp @@ -0,0 +1,76 @@ +using Gtk 4.0; +using Adw 1; + +Adw.StatusPage { + title: _("Scrolled Window"); + description: _("A container that makes its child scrollable"); + + Adw.Clamp { + Box{ + hexpand: true; + vexpand: true; + spacing: 12; + orientation: vertical; + + Box{ + halign: center; + spacing: 18; + + Label { + label: _("Orientation"); + } + + Box { + margin-start: 6; + homogeneous: true; + halign: center; + styles ["linked"] + + ToggleButton toggle_orientation { + label: _("Horizontal"); + active: true; + } + ToggleButton { + label: _("Vertical"); + group: toggle_orientation; + } + } + + Label { + label: _("Go To"); + } + + Box{ + + Button button_start { + label: _("Start"); + } + + Button button_end { + label: _("End"); + } + styles ["linked"] + } + } + + + + ScrolledWindow scrolled_window { + margin-bottom: 24; + margin-top: 24; + has-frame: true; + propagate-natural-height: true; + max-content-height: 300; + + Box container { + homogeneous: true; + } + } + + LinkButton { + label: "API Reference"; + uri: "https://docs.gtk.org/gtk4/class.ScrolledWindow.html"; + } + } + } +} diff --git a/src/Library/demos/Scrolled Window/main.js b/src/Library/demos/Scrolled Window/main.js new file mode 100644 index 000000000..f77f60108 --- /dev/null +++ b/src/Library/demos/Scrolled Window/main.js @@ -0,0 +1,102 @@ +import Gtk from "gi://Gtk"; +import Adw from "gi://Adw"; + +const scrolled_window = workbench.builder.get_object("scrolled_window"); +const container = workbench.builder.get_object("container"); +const toggle_orientation = workbench.builder.get_object("toggle_orientation"); +const button_start = workbench.builder.get_object("button_start"); +const button_end = workbench.builder.get_object("button_end"); +let auto_scrolling = false; + +button_start.sensitive = false; + +const scrollbars = { + [Gtk.Orientation.HORIZONTAL]: scrolled_window.get_hscrollbar(), + [Gtk.Orientation.VERTICAL]: scrolled_window.get_vscrollbar(), +}; + +toggle_orientation.connect("toggled", () => { + if (toggle_orientation.active) { + container.orientation = Gtk.Orientation.HORIZONTAL; + } else { + container.orientation = Gtk.Orientation.VERTICAL; + } +}); + +const num_items = 20; +for (let i = 0; i < num_items; i++) { + populateContainer(container, `Item ${i + 1}`); +} + +for (const orientation in scrollbars) { + const scrollbar = scrollbars[orientation]; + const adj = scrollbar.adjustment; + adj.connect("value-changed", () => { + if (adj.value === adj.lower) { + button_end.sensitive = true; + button_start.sensitive = false; + } else if (adj.value === adj.upper - adj.page_size) { + button_end.sensitive = false; + button_start.sensitive = true; + } else { + // Disable buttons if scrollbar is auto-scrolling + button_end.sensitive = !auto_scrolling; + button_start.sensitive = !auto_scrolling; + } + }); +} + +scrolled_window.connect("edge-reached", () => { + const scrollbar = scrollbars[container.orientation]; + console.log("Edge Reached"); +}); + +button_start.connect("clicked", () => { + auto_scrolling = true; + const scrollbar = scrollbars[container.orientation]; + const anim = createScrollbarAnim(scrollbar, 0); + anim.play(); +}); + +button_end.connect("clicked", () => { + auto_scrolling = true; + const scrollbar = scrollbars[container.orientation]; + const anim = createScrollbarAnim(scrollbar, 1); + anim.play(); +}); + +function populateContainer(container, label) { + const item = new Adw.Bin({ + margin_top: 6, + margin_bottom: 6, + margin_start: 6, + margin_end: 6, + child: new Gtk.Label({ + label: label, + width_request: 100, + height_request: 100, + }), + css_classes: ["card"], + }); + container.append(item); +} + +function createScrollbarAnim(scrollbar, direction) { + // direction = 0 -> Animates to Start + // direction = 1 -> Animates to End + const adjustment = scrollbar.adjustment; + const target = Adw.PropertyAnimationTarget.new(adjustment, "value"); + const animation = new Adw.TimedAnimation({ + widget: scrollbar, + value_from: adjustment.value, + value_to: direction ? adjustment.upper - adjustment.page_size : 0, + duration: 1000, + easing: Adw.Easing["LINEAR"], + target: target, + }); + + animation.connect("done", () => { + auto_scrolling = false; + }); + return animation; +} diff --git a/src/Library/demos/Scrolled Window/main.json b/src/Library/demos/Scrolled Window/main.json new file mode 100644 index 000000000..c422c2ec0 --- /dev/null +++ b/src/Library/demos/Scrolled Window/main.json @@ -0,0 +1,10 @@ +{ + "name": "Scrolled Window", + "category": "layout", + "description": "A container that makes its child scrollable", + "panels": [ + "ui", + "preview" + ], + "autorun": true +} diff --git a/src/Library/demos/Wallpaper/main.vala b/src/Library/demos/Wallpaper/main.vala new file mode 100644 index 000000000..dd7e610cf --- /dev/null +++ b/src/Library/demos/Wallpaper/main.vala @@ -0,0 +1,33 @@ +#!/usr/bin/env -S vala workbench.vala --pkg libadwaita-1 --pkg libportal-gtk4 + +private Xdp.Portal portal; +private Xdp.Parent parent; +private string image_uri; + +public void main () { + portal = new Xdp.Portal (); + parent = Xdp.parent_new_gtk (workbench.window); + image_uri = workbench.resolve ("./wallpaper.png"); + + var button = (Gtk.Button) workbench.builder.get_object ("button"); + button.clicked.connect (on_button_clicked); +} + +private async void on_button_clicked () { + try { + bool success = yield portal.set_wallpaper ( + parent, + image_uri, + PREVIEW | BACKGROUND | LOCKSCREEN, + null + ); + + if (success) { + message ("Wallpaper set successfully"); + return; + } + message ("Could not set wallpaper"); + } catch (Error e) { + critical (e.message); + } +}