Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions boards/qemu_emery/defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ CONFIG_HRM_STUB=y

CONFIG_APP_SCALING=y
CONFIG_MODDABLE_XS=y

CONFIG_ORIENTATION_MANAGER=y
65 changes: 60 additions & 5 deletions src/fw/applib/ui/action_bar_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@
/* SPDX-License-Identifier: Apache-2.0 */

#include "action_bar_layer.h"
#include "action_bar_layer_private.h"

#include "animation.h"
#include "animation_timing.h"
#include "applib/app_timer.h"
#include "applib/applib_malloc.auto.h"
#include "applib/graphics/graphics.h"
#include "applib/ui/window_private.h"
#include "kernel/pebble_tasks.h"
#include "process_management/pebble_process_md.h"
#include "process_management/process_manager.h"
#include "system/passert.h"
#include "pbl/util/trig.h"

#ifdef CONFIG_ORIENTATION_MANAGER
#include "shell/prefs.h"
#include "syscall/syscall.h"
#endif

#ifdef CONFIG_TOUCH
#include "syscall/syscall.h"
#endif
Expand All @@ -31,6 +39,47 @@ static int prv_width(void) {
return _ACTION_BAR_WIDTH(platform);
}

#ifdef CONFIG_ORIENTATION_MANAGER
static bool prv_current_ui_follows_orientation(void) {
const PebbleTask task = pebble_task_get_current();
if (task != PebbleTask_App && task != PebbleTask_Worker) {
return true;
}
const PebbleProcessMd *md = sys_process_manager_get_current_process_md();
return md && process_metadata_get_app_sdk_type(md) == ProcessAppSDKType_System;
}
#endif

bool action_bar_layer_is_on_right(void) {
#ifdef CONFIG_ORIENTATION_MANAGER
if (display_orientation_is_left() && prv_current_ui_follows_orientation()) {
return false;
}
#endif
return true;
}

int16_t action_bar_layer_get_content_origin_x(void) {
return action_bar_layer_is_on_right() ? 0 : prv_width();
}

GRect action_bar_layer_inset_bounds(GRect bounds) {
const int16_t width = prv_width();
bounds.size.w -= width;
if (!action_bar_layer_is_on_right()) {
bounds.origin.x += width;
}
return bounds;
}

GEdgeInsets action_bar_layer_content_insets(int16_t other_margin) {
const int16_t bar_margin = prv_width() + other_margin;
if (action_bar_layer_is_on_right()) {
return GEdgeInsets(0, bar_margin, 0, other_margin);
}
return GEdgeInsets(0, other_margin, 0, bar_margin);
}

static int prv_vertical_icon_margin(void) {
const PlatformType platform = process_manager_current_platform();
return PBL_PLATFORM_SWITCH(platform,
Expand Down Expand Up @@ -139,13 +188,18 @@ static GPoint prv_offset_since_time(int64_t time_ms, int64_t duration_ms,

static GPoint prv_get_button_press_offset(ActionBarLayer *action_bar, uint8_t button_index) {
const int16_t animation_offset = prv_press_animation_offset();
const GPoint offset[5] = {
GPoint offset[5] = {
GPointZero,
GPoint(-animation_offset, 0),
GPoint(0, -animation_offset),
GPoint(animation_offset, 0),
GPoint(0, animation_offset),
};
// MoveLeft is "toward content" when the bar is on the right; flip when it is on the left.
if (!action_bar_layer_is_on_right()) {
offset[ActionBarLayerIconPressAnimationMoveLeft].x = animation_offset;
offset[ActionBarLayerIconPressAnimationMoveRight].x = -animation_offset;
}
return offset[action_bar->animation[button_index]];
}

Expand All @@ -160,7 +214,8 @@ void prv_draw_background_round(ActionBarLayer *action_bar, GContext *ctx, GColor
GRect action_bar_circle_frame = (GRect) {
.size = GSize(action_bar_circle_diameter, action_bar_circle_diameter)
};
grect_align(&action_bar_circle_frame, &action_bar->layer.bounds, GAlignLeft, false /* clips */);
const GAlign align = action_bar_layer_is_on_right() ? GAlignLeft : GAlignRight;
grect_align(&action_bar_circle_frame, &action_bar->layer.bounds, align, false /* clips */);
graphics_fill_oval(ctx, action_bar_circle_frame, GOvalScaleModeFitCircle);
}

Expand Down Expand Up @@ -214,8 +269,8 @@ void action_bar_update_proc(ActionBarLayer *action_bar, GContext* ctx) {
const bool clip = true;
grect_align(&icon_rect, &rect, GAlignCenter, clip);
#if PBL_ROUND
// Offset needed because the new curvature of the action bar makes the icons look off-center
const int32_t icon_horizontal_offset = -2;
// Offset needed because the curvature of the action bar makes the icons look off-center
const int32_t icon_horizontal_offset = action_bar_layer_is_on_right() ? -2 : 2;
icon_rect.origin.x += icon_horizontal_offset;
#endif
icon_rect.origin.x += offset.x;
Expand Down Expand Up @@ -397,7 +452,7 @@ void action_bar_layer_add_to_window(ActionBarLayer *action_bar, struct Window *w
const int16_t width = prv_width();
GRect rect = GRect(0, 0, width, window_bounds->size.h);
layer_set_bounds(&action_bar->layer, &rect);
rect.origin.x = window_bounds->size.w - width;
rect.origin.x = action_bar_layer_is_on_right() ? (window_bounds->size.w - width) : 0;
layer_set_frame(&action_bar->layer, &rect);
layer_add_child(&window->layer, &action_bar->layer);

Expand Down
25 changes: 25 additions & 0 deletions src/fw/applib/ui/action_bar_layer_private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* SPDX-FileCopyrightText: 2026 Core Devices LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#pragma once

#include "action_bar_layer.h"

#include "applib/graphics/gtypes.h"

#include <stdbool.h>
#include <stdint.h>

//! Firmware-only: whether the action bar sits on the right edge of the window.
//! Third-party apps always get the right edge so existing layouts keep working.
//! System and kernel UI follow left-hand display orientation.
bool action_bar_layer_is_on_right(void);

//! X origin of the content area beside the action bar (0 when the bar is on the right).
int16_t action_bar_layer_get_content_origin_x(void);

//! Copy of \a bounds with the action-bar strip removed from the button side.
GRect action_bar_layer_inset_bounds(GRect bounds);

//! Horizontal insets that leave \a other_margin on the content side and room for the bar.
GEdgeInsets action_bar_layer_content_insets(int16_t other_margin);
15 changes: 7 additions & 8 deletions src/fw/applib/ui/action_button.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,28 @@

#include "applib/graphics/graphics.h"
#include "applib/preferred_content_size.h"
#include "applib/ui/action_bar_layer.h"
#include "applib/ui/action_bar_layer_private.h"

void action_button_draw(GContext *ctx, Layer *layer, GColor fill_color) {
// This should match the window bounds
const GRect bounds = layer->bounds;
const bool on_right = action_bar_layer_is_on_right();

// Glue button to the right side of the window
const int radius = PBL_IF_ROUND_ELSE(12, 13);
GRect rect = { .size = { radius * 2, radius * 2 } };
grect_align(&rect, &bounds, GAlignRight, false);
grect_align(&rect, &bounds, on_right ? GAlignRight : GAlignLeft, false);

// Offset the button halfway off-screen
rect.origin.x += radius;
rect.origin.x += on_right ? radius : -radius;

// Further offset the button on a per-default-content-size basis
// Note that this will need to be updated if we ever want ActionButton to adapt to the user's
// preferred content size
rect.origin.x += PREFERRED_CONTENT_SIZE_SWITCH(PreferredContentSizeDefault,
const int extra = PREFERRED_CONTENT_SIZE_SWITCH(PreferredContentSizeDefault,
//! @note this is the same as Medium until Small is designed
/* small */ PBL_IF_ROUND_ELSE(1, 8),
/* medium */ PBL_IF_ROUND_ELSE(1, 8),
/* large */ 4,
//! @note this is the same as Large until ExtraLarge is designed
/* extralarge */ 4);
rect.origin.x += on_right ? extra : -extra;

graphics_context_set_fill_color(ctx, fill_color);
graphics_fill_oval(ctx, rect, GOvalScaleModeFitCircle);
Expand Down
23 changes: 15 additions & 8 deletions src/fw/applib/ui/dialogs/actionable_dialog.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "applib/applib_malloc.auto.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/action_bar_layer_private.h"
#include "applib/ui/bitmap_layer.h"
#include "applib/ui/dialogs/dialog.h"
#include "applib/ui/dialogs/dialog_private.h"
Expand All @@ -31,13 +32,17 @@ static void prv_actionable_dialog_load(Window *window) {
const GRect *bounds = &window_root_layer->bounds;
const uint16_t left_margin_px = PBL_IF_RECT_ELSE(5, 0);
const uint16_t content_and_action_bar_horizontal_spacing = PBL_IF_RECT_ELSE(5, 7);
const uint16_t content_x_start = action_bar_layer_get_content_origin_x();
#if PBL_ROUND
const bool action_bar_on_right = action_bar_layer_is_on_right();
#endif
const uint16_t right_margin_px = ACTION_BAR_WIDTH +
content_and_action_bar_horizontal_spacing;
const GFont dialog_text_font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
const int max_text_line_height_px = 2 * fonts_get_font_height(dialog_text_font) + 8;
const uint16_t icon_text_spacing_px = 4;

uint16_t x = 0;
uint16_t x = content_x_start;
uint16_t y = 0;
uint16_t w = PBL_IF_RECT_ELSE(bounds->size.w - ACTION_BAR_WIDTH, bounds->size.w);
uint16_t h = STATUS_BAR_LAYER_HEIGHT;
Expand All @@ -46,7 +51,8 @@ static void prv_actionable_dialog_load(Window *window) {
dialog_add_status_bar_layer(dialog, &GRect(x, y, w, h));
}

x = left_margin_px;
x = content_x_start + (action_bar_layer_is_on_right()
? left_margin_px : content_and_action_bar_horizontal_spacing);
w = bounds->size.w - left_margin_px - right_margin_px;

GTextAttributes *text_attributes = NULL;
Expand All @@ -60,7 +66,8 @@ static void prv_actionable_dialog_load(Window *window) {

// Probe text height to vertically center icon + text in the available space
GContext *ctx = graphics_context_get_current_context();
const GTextAlignment text_alignment = PBL_IF_RECT_ELSE(GTextAlignmentCenter, GTextAlignmentRight);
const GTextAlignment text_alignment = PBL_IF_RECT_ELSE(GTextAlignmentCenter,
action_bar_on_right ? GTextAlignmentRight : GTextAlignmentLeft);
const GRect probe_rect = GRect(x, 0, w, max_text_line_height_px);
const uint16_t text_height = graphics_text_layout_get_max_used_size(ctx,
dialog->buffer,
Expand Down Expand Up @@ -130,12 +137,12 @@ static void prv_actionable_dialog_load(Window *window) {
// On rectangular displays we just center it horizontally b/w the left edge of the display and
// the left edge of the action bar
#if PBL_RECT
x = (grect_get_max_x(bounds) - ACTION_BAR_WIDTH - icon_size.w) / 2;
x = content_x_start + (grect_get_max_x(bounds) - ACTION_BAR_WIDTH - icon_size.w) / 2;
#else
// On round displays we right align it with respect to the same imaginary vertical line that the
// text is right aligned to
x = grect_get_max_x(bounds) - ACTION_BAR_WIDTH - content_and_action_bar_horizontal_spacing -
icon_size.w;
x = action_bar_on_right
? (grect_get_max_x(bounds) - ACTION_BAR_WIDTH - content_and_action_bar_horizontal_spacing -
icon_size.w)
: (content_x_start + content_and_action_bar_horizontal_spacing);
#endif

y = icon_top_margin_px;
Expand Down
25 changes: 16 additions & 9 deletions src/fw/applib/ui/dialogs/expandable_dialog.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "applib/graphics/gtypes.h"
#include "applib/graphics/text.h"
#include "applib/ui/bitmap_layer.h"
#include "applib/ui/action_bar_layer_private.h"
#include "applib/ui/dialogs/dialog_private.h"
#include "applib/ui/layer.h"
#include "applib/ui/text_layer.h"
Expand Down Expand Up @@ -106,6 +107,7 @@ static void prv_expandable_dialog_load(Window *window) {
const uint16_t NM_LEFT_MARGIN_PX = 10;

bool show_action_bar = expandable_dialog->show_action_bar;
const bool action_bar_on_right = action_bar_layer_is_on_right();

uint16_t left_margin_px = show_action_bar ? SM_LEFT_MARGIN_PX : NM_LEFT_MARGIN_PX;
uint16_t right_margin_px = left_margin_px;
Expand All @@ -117,8 +119,9 @@ static void prv_expandable_dialog_load(Window *window) {

uint16_t status_layer_offset = dialog->show_status_layer * STATUS_BAR_LAYER_HEIGHT;
uint16_t action_bar_offset = show_action_bar * ACTION_BAR_WIDTH;
uint16_t content_x_start = (show_action_bar && !action_bar_on_right) ? action_bar_offset : 0;

uint16_t x = 0;
uint16_t x = content_x_start;
uint16_t y = 0;
uint16_t w = PBL_IF_RECT_ELSE(frame.size.w - action_bar_offset, frame.size.w);
uint16_t h = STATUS_BAR_LAYER_HEIGHT;
Expand Down Expand Up @@ -147,16 +150,18 @@ static void prv_expandable_dialog_load(Window *window) {
// Set up the header if this dialog is set to have one.
GTextAlignment alignment = PBL_IF_RECT_ELSE(GTextAlignmentLeft,
(show_action_bar ?
GTextAlignmentRight : GTextAlignmentCenter));
(action_bar_on_right ?
GTextAlignmentRight : GTextAlignmentLeft) :
GTextAlignmentCenter));
uint16_t right_aligned_box_reduction = PBL_IF_RECT_ELSE(0, show_action_bar ? 10 : 0);
if (has_header) {
const uint16_t HEADER_OFFSET = 6;
#if PBL_RECT
x = left_margin_px;
x = content_x_start + left_margin_px;
w = frame.size.w - right_margin_px - left_margin_px - action_bar_offset
- right_aligned_box_reduction;
#else
x = 0;
x = content_x_start;
w = frame.size.w - right_margin_px - action_bar_offset - right_aligned_box_reduction;
#endif
y = icon ? icon_offset + icon_size.h : -HEADER_OFFSET;
Expand All @@ -183,7 +188,7 @@ static void prv_expandable_dialog_load(Window *window) {

// Set up the text.
const uint16_t TEXT_OFFSET = 6;
x = left_margin_px;
x = content_x_start + left_margin_px;
y = (icon ? icon_offset + icon_size.h : -TEXT_OFFSET) + header_content_height;
w = frame.size.w - right_margin_px - left_margin_px - action_bar_offset
- right_aligned_box_reduction;
Expand Down Expand Up @@ -248,9 +253,11 @@ static void prv_expandable_dialog_load(Window *window) {
window_set_click_config_provider_with_context(window, prv_config_provider, expandable_dialog);
}

x = PBL_IF_RECT_ELSE(left_margin_px, (show_action_bar) ?
(frame.size.w - right_margin_px - left_margin_px -
action_bar_offset - right_aligned_box_reduction - icon_size.h) :
x = PBL_IF_RECT_ELSE(content_x_start + left_margin_px, (show_action_bar) ?
(action_bar_on_right ?
(frame.size.w - right_margin_px - left_margin_px -
action_bar_offset - right_aligned_box_reduction - icon_size.h) :
(content_x_start + left_margin_px)) :
(90 - icon_size.h / 2));
y = icon_offset + PBL_IF_RECT_ELSE(0, 5);
if (dialog_init_icon_layer(dialog, icon, GPoint(x, y), false /* not animated */)) {
Expand All @@ -275,7 +282,7 @@ static void prv_expandable_dialog_load(Window *window) {
.colors.background = dialog->window.background_color,
});
layer_init(&expandable_dialog->content_down_arrow_layer, &GRect(
0, frame.size.h - CONTENT_DOWN_ARROW_HEIGHT,
content_x_start, frame.size.h - CONTENT_DOWN_ARROW_HEIGHT,
PBL_IF_RECT_ELSE(frame.size.w - action_bar_offset, frame.size.w),
CONTENT_DOWN_ARROW_HEIGHT));
layer_add_child(&window->layer, &expandable_dialog->content_down_arrow_layer);
Expand Down
4 changes: 2 additions & 2 deletions src/fw/applib/ui/number_window.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "applib/fonts/fonts.h"
#include "applib/graphics/graphics.h"
#include "applib/applib_malloc.auto.h"
#include "applib/ui/action_bar_layer_private.h"
#include "kernel/ui/kernel_ui.h"
#include "kernel/ui/system_icons.h"
#include "pbl/util/size.h"
Expand Down Expand Up @@ -75,8 +76,7 @@ static GRect prv_get_text_frame(Layer *window_layer) {
const int16_t x_margin = 5;
const int16_t label_y_offset = PBL_IF_ROUND_ELSE(40, 16);
const GEdgeInsets insets = PBL_IF_ROUND_ELSE(GEdgeInsets(ACTION_BAR_WIDTH + x_margin),
GEdgeInsets(0, ACTION_BAR_WIDTH + x_margin, 0,
x_margin));
action_bar_layer_content_insets(x_margin));
GRect frame = grect_inset(window_layer->bounds, insets);
frame.origin.y = label_y_offset;
return frame;
Expand Down
Loading