mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-09 04:30:49 -04:00
UI: Mobile keyboard fixes, safe area padding, & more
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user