UI: Mobile keyboard fixes, safe area padding, & more

This commit is contained in:
Luke Street
2026-04-30 00:06:27 -06:00
parent b86d6e90e2
commit 4a12554bf4
8 changed files with 152 additions and 11 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ bool Component::focus() {
if (mRoot->Focus(true)) {
mRoot->ScrollIntoView(Rml::ScrollIntoViewOptions{
Rml::ScrollAlignment::Center,
Rml::ScrollAlignment::Nearest,
Rml::ScrollAlignment::Center,
Rml::ScrollBehavior::Smooth,
Rml::ScrollParentage::Closest,
});
+28 -6
View File
@@ -1,5 +1,7 @@
#include "string_button.hpp"
#include <aurora/rmlui.hpp>
namespace dusk::ui {
BaseStringButton::BaseStringButton(Rml::Element* parent, Props props)
@@ -12,6 +14,12 @@ void BaseStringButton::update() {
if (mPendingStopEditing) {
stop_editing(mPendingCommit, mPendingRefocusRoot);
}
if (mPendingInputFocusFrames > 0) {
--mPendingInputFocusFrames;
if (mPendingInputFocusFrames == 0) {
focus_input();
}
}
ControlledSelectButton::update();
}
@@ -37,10 +45,9 @@ void BaseStringButton::start_editing() {
// Hide value element
mValueElem->SetProperty(Rml::PropertyId::Visibility, Rml::Style::Visibility::Hidden);
// Focus and select text within input
mInputElem->Focus(true);
const int end = static_cast<int>(Rml::StringUtilities::LengthUTF8(mInputElem->GetValue()));
mInputElem->SetSelectionRange(0, end);
// RmlUi lays out the new input during render. Wait one full frame before focusing it so
// mobile keyboard placement gets a valid caret rectangle.
mPendingInputFocusFrames = 2;
// Mark button as selected to indicate "active"
set_selected(true);
@@ -84,11 +91,26 @@ bool BaseStringButton::handle_nav_command(NavCommand cmd) {
return false;
}
void BaseStringButton::stop_editing(bool commit, bool refocusRoot) {
void BaseStringButton::focus_input() {
if (mInputElem == nullptr) {
return;
}
aurora::rmlui::set_input_type(
mType == "number" ? aurora::rmlui::InputType::Number : aurora::rmlui::InputType::Text);
if (mInputElem->Focus(true)) {
const int end = static_cast<int>(Rml::StringUtilities::LengthUTF8(mInputElem->GetValue()));
mInputElem->SetSelectionRange(0, end);
}
}
void BaseStringButton::stop_editing(bool commit, bool refocusRoot) {
mPendingStopEditing = false;
mPendingInputFocusFrames = 0;
if (mInputElem == nullptr) {
return;
}
if (commit) {
set_value(mInputElem->GetValue());
}
@@ -109,4 +131,4 @@ StringButton::StringButton(Rml::Element* parent, Props props)
: BaseStringButton(parent, {.key = std::move(props.key), .maxLength = props.maxLength}),
mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)) {}
} // namespace dusk::ui
} // namespace dusk::ui
+3 -1
View File
@@ -24,12 +24,14 @@ protected:
virtual void set_value(Rml::String value) = 0;
private:
void focus_input();
void stop_editing(bool commit = true, bool refocusRoot = false);
Rml::ElementFormControlInput* mInputElem = nullptr;
std::vector<std::unique_ptr<ScopedEventListener> > mInputListeners;
Rml::String mType;
int mMaxLength;
int mPendingInputFocusFrames = 0;
bool mPendingStopEditing = false;
bool mPendingCommit = true;
bool mPendingRefocusRoot = false;
@@ -59,4 +61,4 @@ private:
std::function<void(Rml::String)> mSetValue;
};
} // namespace dusk::ui
} // namespace dusk::ui
+84
View File
@@ -1,13 +1,67 @@
#include "window.hpp"
#include <RmlUi/Core.h>
#include <SDL3/SDL_video.h>
#include "aurora/lib/window.hpp"
#include "aurora/rmlui.hpp"
#include "button.hpp"
#include "magic_enum.hpp"
#include "ui.hpp"
#include <algorithm>
#include <cmath>
namespace dusk::ui {
namespace {
float base_body_padding(Rml::Context* context) noexcept {
if (context == nullptr) {
return 64.0f;
}
const float dpRatio = std::max(context->GetDensityIndependentPixelRatio(), 0.001f);
const float heightDp = static_cast<float>(context->GetDimensions().y) / dpRatio;
if (heightDp <= 640.0f) {
return 16.0f * dpRatio;
}
return 64.0f * dpRatio;
}
Window::Insets safe_area_insets(Rml::Context* context) noexcept {
if (context == nullptr) {
return {};
}
auto* window = aurora::window::get_sdl_window();
if (window == nullptr) {
return {};
}
const AuroraWindowSize windowSize = aurora::window::get_window_size();
if (windowSize.width == 0 || windowSize.height == 0) {
return {};
}
SDL_Rect safeRect{};
if (!SDL_GetWindowSafeArea(window, &safeRect)) {
return {};
}
const Rml::Vector2i contextSize = context->GetDimensions();
const float scaleX = static_cast<float>(contextSize.x) / static_cast<float>(windowSize.width);
const float scaleY = static_cast<float>(contextSize.y) / static_cast<float>(windowSize.height);
const float safeRight = static_cast<float>(safeRect.x + safeRect.w);
const float safeBottom = static_cast<float>(safeRect.y + safeRect.h);
return {
.top = std::max(0.0f, static_cast<float>(safeRect.y)) * scaleY,
.right = std::max(0.0f, static_cast<float>(windowSize.width) - safeRight) * scaleX,
.bottom = std::max(0.0f, static_cast<float>(windowSize.height) - safeBottom) * scaleY,
.left = std::max(0.0f, static_cast<float>(safeRect.x)) * scaleX,
};
}
} // namespace
Window::Window() {
auto* context = aurora::rmlui::get_context();
@@ -74,11 +128,41 @@ void Window::hide() {
}
void Window::update() {
update_safe_area();
for (const auto& component : mContentComponents) {
component->update();
}
}
void Window::update_safe_area() noexcept {
if (mDocument == nullptr) {
return;
}
Rml::Context* context = mDocument->GetContext();
const float basePadding = base_body_padding(context);
Insets safeInsets = safe_area_insets(context);
safeInsets = {
std::round(std::max(basePadding, safeInsets.top)),
std::round(std::max(basePadding, safeInsets.right)),
std::round(std::max(basePadding, safeInsets.bottom)),
std::round(std::max(basePadding, safeInsets.left)),
};
if (safeInsets == mBodyPadding) {
return;
}
mBodyPadding = safeInsets;
mDocument->SetProperty(
Rml::PropertyId::PaddingTop, Rml::Property(safeInsets.top, Rml::Unit::PX));
mDocument->SetProperty(
Rml::PropertyId::PaddingRight, Rml::Property(safeInsets.right, Rml::Unit::PX));
mDocument->SetProperty(
Rml::PropertyId::PaddingBottom, Rml::Property(safeInsets.bottom, Rml::Unit::PX));
mDocument->SetProperty(
Rml::PropertyId::PaddingLeft, Rml::Property(safeInsets.left, Rml::Unit::PX));
}
bool Window::set_active_tab(int index) {
if (index < 0 || index >= mTabs.size() || index == mSelectedTabIndex) {
return false;
+13
View File
@@ -17,6 +17,17 @@ public:
std::unique_ptr<Button> button;
TabBuilder builder;
};
struct Insets {
float top = 0.0f;
float right = 0.0f;
float bottom = 0.0f;
float left = 0.0f;
bool operator==(const Insets& other) const noexcept {
return top == other.top && right == other.right && bottom == other.bottom &&
left == other.left;
}
};
Window();
~Window();
@@ -32,6 +43,7 @@ public:
protected:
void add_tab(const Rml::String& title, TabBuilder builder);
void update_safe_area() noexcept;
void clear_content() noexcept;
bool focus_active_tab() noexcept;
bool handle_tab_bar_nav(Rml::Event& event, NavCommand cmd) noexcept;
@@ -48,6 +60,7 @@ protected:
Rml::ElementDocument* mDocument = nullptr;
std::vector<Tab> mTabs;
std::vector<std::unique_ptr<Component> > mContentComponents;
Insets mBodyPadding;
int mSelectedTabIndex = 0;
std::unique_ptr<ScopedEventListener> mKeyListener;
};
+2 -2
View File
@@ -590,9 +590,9 @@ int game_main(int argc, char* argv[]) {
// TODO: just for testing
auto& editorWindow = dusk::ui::add_window(std::make_unique<dusk::ui::EditorWindow>());
// editorWindow.show();
editorWindow.show();
auto& settingsWindow = dusk::ui::add_window(std::make_unique<dusk::ui::SettingsWindow>());
settingsWindow.show();
// settingsWindow.show();
std::string dvd_path;
bool dvd_opened = false;