Add optional smooth scrolling across UI scrollable widgets

Introduce UIScrollController to provide frame-rate-independent wheel
interpolation, touch dragging, and momentum for code editors, terminals,
scroll views, and scrollable widgets while preserving the legacy instant
scrolling path.

Add a UISceneNode-level smooth-scrolling policy. Embedded scenes inherit
the root scene setting, new widgets snapshot it during construction, and
setSmoothScrollEnabled() can optionally apply changes to existing widgets
across the complete embedded-scene hierarchy.

Move native macOS scrolling and title-bar helpers into PlatformHelper,
enable native momentum from UIApplication, and explicitly enable it in
ecode. Preserve precise SDL2 and SDL3 wheel deltas for fractional momentum
events without changing normal integer wheel steps.

Replace the separate editor and terminal preferences in ecode and eterm
with one global smooth-scrolling setting, including legacy configuration
migration, live updates, and localized settings text.

Improve UITouchDraggableWidget momentum handling and add regression tests
for scrolling interpolation, pixel-aligned editor positions, scene
inheritance, widget overrides, and immediate policy application.
This commit is contained in:
Martín Lucas Golini
2026-09-13 20:27:37 -03:00
parent 42de99cf3b
commit 071bc5d697
46 changed files with 1250 additions and 305 deletions
+24 -3
View File
@@ -35,12 +35,26 @@ Always regenerate the project files before compiling or running tests after maki
* **Tool:** Use `premake4` if installed; otherwise, fallback to `premake5` (the parameters are identical).
* **Linker Flag (`--with-mold-linker`):** This flag is conditional. If the `mold` linker is installed on the system, you **must** include it to speed up linking. If `mold` is not installed, omit the flag.
**Command (if `mold` is installed):**
Choose the generator command from the configuration that will be built. Debug builds use AddressSanitizer; release builds do not.
**Release command (if `mold` is installed):**
`premake4 --disable-static-build --with-mold-linker --with-debug-symbols gmake`
**Release command (if `mold` is NOT installed):**
`premake4 --disable-static-build --with-debug-symbols gmake`
**Debug command (if `mold` is installed):**
`premake4 --disable-static-build --with-mold-linker --with-debug-symbols --address-sanitizer gmake`
**Command (if `mold` is NOT installed):**
**Debug command (if `mold` is NOT installed):**
`premake4 --disable-static-build --with-debug-symbols --address-sanitizer gmake`
Never add `--address-sanitizer` when generating project files for a release build, and never omit it
when generating project files for a debug build. Always regenerate immediately before building so
the generated files match the intended configuration. Generator options are not tracked as object
dependencies, so when switching sanitizer mode, clean the affected configuration before rebuilding
to avoid mixing instrumented and uninstrumented C++ objects.
## Step 1a: Format Changed Files
After editing any C or C++ source file (`.c`, `.cpp`, `.h`, `.hpp`), you **must** run `clang-format` on all modified files to ensure consistent formatting with the project's style (defined in `.clang-format` at the repository root).
@@ -60,6 +74,12 @@ Always use all processors reported by the platform when selecting the parallel j
and other systems with `nproc`, use `-j$(nproc)` exactly; do not substitute an arbitrary fixed value
such as `-j4`. Use the platform-equivalent processor-count command where `nproc` is unavailable.
On macOS in a managed or sandboxed environment, request elevated permission before invoking
`sysctl` or running any command that needs access to host display services. Read and validate
`sysctl -n hw.ncpu` separately before starting the build, then pass the verified positive value as a
literal `-jN` argument. If the query fails, returns zero, or returns an empty value, stop and request
permission; never allow an empty command substitution to turn `-j` into unbounded parallelism.
The valid OS directory names are: `windows`, `macosx`, `linux`, `bsd`, `haiku`.
Run the following command, replacing `<os_name>` with the correct environment:
@@ -67,7 +87,8 @@ Run the following command, replacing `<os_name>` with the correct environment:
**Examples:**
* Linux: `make -C make/linux -j$(nproc)`
* macOS: `make -C make/macosx -j$(sysctl -n hw.ncpu)`
* macOS: after an approved `sysctl -n hw.ncpu` returns a value such as `10`, run
`make -C make/macosx -j10` using that exact verified value.
* Windows: `make -C make/windows -j%NUMBER_OF_PROCESSORS%`
## Running GUI Examples Under Xvfb
-2
View File
@@ -4,5 +4,3 @@
## UI Module
* Implement support for very simple state-changes from the XML file (ex: onclick="toggleclass(x)").
* Implement smooth-scrolling for macOS
+2
View File
@@ -1146,6 +1146,8 @@ Für sichtbare Änderung ecode neu starten.</string>
<string name="scrollbar_type">Bildlaufleistentyp</string>
<string name="scroll_outside_tooltip">Die Bildlaufleiste hat eigenen Platz und verdeckt nie den Inhalt.</string>
<string name="scroll_overlay_tooltip">Die Bildlaufleiste wird über dem Inhalt angezeigt.</string>
<string name="smooth_scroll">Sanftes Scrollen</string>
<string name="smooth_scroll_desc">Animiert das Scrollen mit Mausrad und Trackpad.</string>
<string name="set_screenshot_save_path">Speicherpfad für Bildschirmfotos</string>
<string name="slight">Leicht</string>
<string name="steady_bar">Statischer Balken</string>
+2
View File
@@ -1131,6 +1131,8 @@ Restart ecode to see the changes.</string>
<string name="scrollbar_type">Scrollbar Type</string>
<string name="scroll_outside_tooltip">The scrollbar has its own space and never covers content.</string>
<string name="scroll_overlay_tooltip">The scrollbar appears over content.</string>
<string name="smooth_scroll">Smooth Scrolling</string>
<string name="smooth_scroll_desc">Animate scrolling from mouse wheels and trackpads.</string>
<string name="set_screenshot_save_path">Screenshot Save Path</string>
<string name="slight">Slight</string>
<string name="steady_bar">Steady Bar</string>
+2
View File
@@ -1130,6 +1130,8 @@ Redémarrer ecode pour voir les changements.</string>
<string name="scrollbar_type">Type de barre de défilement</string>
<string name="scroll_outside_tooltip">La barre de défilement occupe son propre espace et ne recouvre jamais le contenu.</string>
<string name="scroll_overlay_tooltip">La barre de défilement apparaît par-dessus le contenu.</string>
<string name="smooth_scroll">Défilement fluide</string>
<string name="smooth_scroll_desc">Anime le défilement avec la molette de la souris et le pavé tactile.</string>
<string name="set_screenshot_save_path">Chemin d'enregistrement des captures d'écran</string>
<string name="slight">Léger</string>
<string name="steady_bar">Barre fixe</string>
+2
View File
@@ -924,6 +924,8 @@ file in the directory tree.</string>
<string name="scrollbar_type">滚动条类型</string>
<string name="scroll_outside_tooltip">滚动条占用独立空间,不会覆盖内容。</string>
<string name="scroll_overlay_tooltip">滚动条叠加显示在内容上方。</string>
<string name="smooth_scroll">平滑滚动</string>
<string name="smooth_scroll_desc">为鼠标滚轮和触控板触发的滚动添加动画。</string>
<string name="session_snapshot">会话快照与定期备份</string>
<string name="session_snapshot_desc">保留会话之间未保存的文档更改。</string>
<string name="set_screenshot_save_path">截图保存路径</string>
+2 -2
View File
@@ -2124,8 +2124,8 @@ class EE_API Node : public Transformable {
/**
* @brief Handles mouse wheel scroll events.
*
* Called when the mouse wheel is scrolled. Default implementation returns 1
* to stop propagation but does not forward the event.
* Called when the mouse wheel is scrolled. Default implementation returns 0 so the event can
* bubble to an ancestor that implements scrolling.
*
* @param offset Scroll offset vector.
* @param flipped Whether the scroll direction is flipped (e.g., on Mac).
+1
View File
@@ -160,6 +160,7 @@
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uiscrollablewidget.hpp>
#include <eepp/ui/uiscrollbar.hpp>
#include <eepp/ui/uiscrollcontroller.hpp>
#include <eepp/ui/uiscrollview.hpp>
#include <eepp/ui/uiselectbutton.hpp>
#include <eepp/ui/uiskin.hpp>
+1
View File
@@ -162,6 +162,7 @@ enum class PropertyId : Uint16 {
AllowFloat,
TouchDrag,
TouchDragDeceleration,
ScrollBehavior,
WindowTitle,
WindowOpacity,
WindowButtonsOffset,
+12 -2
View File
@@ -11,7 +11,7 @@
#include <eepp/ui/keyboardshortcut.hpp>
#include <eepp/ui/mouseshortcut.hpp>
#include <eepp/ui/uifontstyleconfig.hpp>
#include <eepp/ui/uiwidget.hpp>
#include <eepp/ui/uitouchdraggablewidget.hpp>
#include <unordered_map>
#include <unordered_set>
@@ -197,7 +197,7 @@ class EE_API DocSyntaxDefEvent : public DocEvent {
std::string newLang;
};
class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
class EE_API UICodeEditor : public UITouchDraggableWidget, public TextDocument::Client {
public:
struct MinimapConfig {
Float width{ 100 }; // dp width
@@ -1087,6 +1087,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
virtual Uint32 onMouseWheel( const Vector2f& offset, bool flipped );
virtual Uint32 onMouseClick( const Vector2i& position, const Uint32& flags );
virtual Uint32 onMouseDoubleClick( const Vector2i& position, const Uint32& flags );
@@ -1187,6 +1189,14 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
virtual Uint32 onMessage( const NodeMessage* msg );
virtual bool supportsScrollController() const;
virtual Vector2f getScrollControllerPosition() const;
virtual Vector2f getScrollControllerMaxPosition() const;
virtual void setScrollControllerPosition( const Vector2f& position );
void checkMouseOverColor( const Vector2i& position );
String checkMouseOverLink( const Vector2i& position, bool checkModifiers = true );
+13
View File
@@ -775,6 +775,18 @@ class EE_API UISceneNode : public SceneNode {
Uint32 getDefaultTextHints() const;
/**
* @brief Sets the default smooth-scrolling policy for this UI scene hierarchy.
*
* Embedded UI scenes inherit the value from their root host scene. New scrollable widgets
* snapshot this value when they are created and can override it individually. When applyNow is
* true, all existing scrollable widgets in the hierarchy are updated too.
*/
UISceneNode* setSmoothScrollEnabled( bool enabled, bool applyNow = false );
/** @return The smooth-scrolling default inherited from the root host UI scene. */
bool isSmoothScrollEnabled() const;
static Uint32 resolveTextHints( Uint32 defaultHints, Uint32 overrideValue,
Uint32 overrideMask );
@@ -1034,6 +1046,7 @@ class EE_API UISceneNode : public SceneNode {
Sizef mLayoutViewportPixelsSize;
bool mHasLayoutViewportPixelsSize{ false };
bool mFollowParentSize{ true };
bool mSmoothScrollEnabled{ false };
bool mOwnsEventDispatcher{ true };
std::shared_ptr<ThreadPool> mThreadPool;
URI mURI;
+11 -1
View File
@@ -7,7 +7,7 @@ namespace EE { namespace UI {
class UIScrollBar;
class EE_API UIScrollableWidget : public UIWidget {
class EE_API UIScrollableWidget : public UITouchDraggableWidget {
public:
virtual Uint32 getType() const;
@@ -89,6 +89,16 @@ class EE_API UIScrollableWidget : public UIWidget {
virtual void updateScroll();
virtual void onScrollChange();
virtual Uint32 onMouseWheel( const Vector2f& offset, bool flipped );
virtual bool supportsScrollController() const;
virtual Vector2f getScrollControllerPosition() const;
virtual Vector2f getScrollControllerMaxPosition() const;
virtual void setScrollControllerPosition( const Vector2f& position );
};
}} // namespace EE::UI
+85
View File
@@ -0,0 +1,85 @@
#ifndef EE_UI_UISCROLLCONTROLLER_HPP
#define EE_UI_UISCROLLCONTROLLER_HPP
#include <array>
#include <eepp/math/vector2.hpp>
#include <eepp/system/time.hpp>
using namespace EE::Math;
using namespace EE::System;
namespace EE { namespace UI {
/**
* @brief Allocation-free, frame-rate-independent scroll motion state.
*
* The controller operates in pixel coordinates but deliberately does not own a widget. This lets
* virtualized views, translated child views, and text editors share the same motion without
* forcing them into a common rendering hierarchy.
*/
class EE_API UIScrollController {
public:
UIScrollController();
/** Synchronizes the controller with an externally changed scroll position and stops motion. */
void setPosition( const Vector2f& position, const Vector2f& maxPosition );
/** Adds a wheel displacement. Returns false when the target cannot move in either axis. */
bool scrollBy( const Vector2f& delta, const Vector2f& currentPosition,
const Vector2f& maxPosition, bool smooth, Float durationScale = 1.f );
/** Starts direct manipulation at the current scroll position. */
void beginDrag( const Vector2f& position, const Vector2f& maxPosition );
/** Applies a drag displacement and records a filtered release velocity. */
bool dragBy( const Vector2f& delta, const Time& elapsed, const Vector2f& maxPosition );
/** Transitions a direct manipulation into inertial motion. */
void endDrag();
/** Advances smooth or inertial motion. Returns true when the position changed. */
bool update( const Time& elapsed, const Vector2f& maxPosition );
/** Stops all pending motion at the current controller position. */
void stop();
bool isActive() const;
bool isDragging() const;
const Vector2f& getPosition() const;
const Vector2f& getTarget() const;
const Vector2f& getVelocity() const;
Time getWheelScrollDuration() const;
/** Sets the duration used to visually interpolate one wheel displacement. */
void setWheelScrollDuration( const Time& duration );
Vector2f getDragDeceleration() const;
void setDragDeceleration( const Vector2f& deceleration );
protected:
enum class Mode : Uint8 { Idle, Smooth, Dragging, Momentum };
struct SmoothTween {
Vector2f delta;
Float elapsed{ 0.f };
Float duration{ 0.f };
};
Vector2f mPosition;
Vector2f mTarget;
Vector2f mVelocity;
Vector2f mDragDeceleration{ 5.f, 5.f };
std::array<SmoothTween, 32> mSmoothTweens;
Time mWheelScrollDuration{ Milliseconds( 100 ) };
Uint32 mSmoothTweenCount{ 0 };
Mode mMode{ Mode::Idle };
};
}} // namespace EE::UI
#endif // EE_UI_UISCROLLCONTROLLER_HPP
+10
View File
@@ -106,6 +106,16 @@ class EE_API UIScrollView : public UITouchDraggableWidget {
virtual bool isTouchOverAllowedChildren();
virtual Uint32 onMouseWheel( const Vector2f& offset, bool flipped );
virtual bool supportsScrollController() const;
virtual Vector2f getScrollControllerPosition() const;
virtual Vector2f getScrollControllerMaxPosition() const;
virtual void setScrollControllerPosition( const Vector2f& position );
virtual void onParentChange();
void listenParent();
+28 -1
View File
@@ -1,6 +1,7 @@
#ifndef EE_UITOUCHDRAGGABLEWIDGET_HPP
#define EE_UITOUCHDRAGGABLEWIDGET_HPP
#include <eepp/ui/uiscrollcontroller.hpp>
#include <eepp/ui/uiwidget.hpp>
namespace EE { namespace UI {
@@ -29,6 +30,11 @@ class EE_API UITouchDraggableWidget : public UIWidget {
UITouchDraggableWidget* setTouchDragDeceleration( const Vector2f& touchDragDeceleration );
/** Enables interpolation of discrete wheel input. Disabled by default. */
bool isSmoothScrollEnabled() const;
UITouchDraggableWidget* setSmoothScrollEnabled( bool enabled );
virtual bool applyProperty( const StyleSheetProperty& attribute );
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
@@ -38,8 +44,11 @@ class EE_API UITouchDraggableWidget : public UIWidget {
protected:
Vector2f mTouchDragPoint;
Vector2f mTouchDragAcceleration;
Vector2f mTouchDragVelocity;
Vector2f mTouchDragDeceleration;
UIScrollController mScrollController;
bool mSmoothScrollEnabled{ false };
bool mApplyingScrollController{ false };
UITouchDraggableWidget( const std::string& tag );
@@ -47,6 +56,24 @@ class EE_API UITouchDraggableWidget : public UIWidget {
virtual bool isTouchOverAllowedChildren();
/** Returns true for widgets that expose an absolute pixel scroll position to the controller. */
virtual bool supportsScrollController() const;
virtual Vector2f getScrollControllerPosition() const;
virtual Vector2f getScrollControllerMaxPosition() const;
virtual void setScrollControllerPosition( const Vector2f& position );
/** Adds a pixel-space wheel displacement using the configured scroll behavior. */
bool scrollBy( const Vector2f& delta, Float durationScale = 1.f );
/** Preserves sub-tick precision without allowing one native event to exceed a legacy step. */
static Float getWheelScrollFactor( Float offset );
/** Synchronizes externally initiated scrolling and cancels pending motion. */
void stopScrollController();
virtual void scheduledUpdate( const Time& time );
virtual Uint32 onMessage( const NodeMessage* msg );
+15
View File
@@ -20,6 +20,21 @@ class EE_API PlatformHelper {
virtual void iconvFree( char* buf ) = 0;
/** Enables or disables momentum generated by the native window system for scroll devices.
* @return true when the platform supports changing this setting.
*/
virtual bool setNativeScrollMomentumEnabled( bool enabled );
/** Controls the native separator below a window title bar.
* @return true when the platform and window support changing this setting.
*/
virtual bool setWindowTitleBarSeparatorVisible( void* window, bool visible );
/** Sets a native window title bar color from 8-bit RGB components.
* @return true when the platform and window support changing this setting.
*/
virtual bool setWindowTitleBarColor( void* window, Uint8 red, Uint8 green, Uint8 blue );
#if EE_PLATFORM == EE_PLATFORM_ANDROID
/** @return The Activity object for the application */
virtual void* getActivity() = 0;
+2 -26
View File
@@ -1643,7 +1643,8 @@ solution "eepp"
language "C++"
set_targetdir("libs/" .. os.get_real() .. "/")
includedirs { "include", "src" }
files { "src/eepp/ui/platform/macos/macosmenubar.mm" }
files { "src/eepp/ui/platform/macos/macosmenubar.mm",
"src/eepp/window/platform/macos/platformhelper.mm" }
buildoptions { "-x objective-c++" }
if not is_vs() then
buildoptions{ "-std=c++20" }
@@ -1869,30 +1870,6 @@ solution "eepp"
files { "src/tools/uieditor/*.cpp" }
build_link_configuration( "eepp-UIEditor", true )
if os.is("macosx") then
project "ecode-macos-helper-static"
kind "StaticLib"
language "C++"
files { "src/tools/ecode/macos/*.m" }
set_targetdir("libs/" .. os.get_real() .. "/thirdparty/")
configuration "debug"
defines { "DEBUG", "EE_DEBUG", "EE_MEMORY_MANAGER" }
flags { "Symbols" }
buildoptions{ "-Wall" }
buildoptions{ "-g3" }
targetname ( "ecode-macos-helper-static-debug" )
configuration "release"
defines { "NDEBUG" }
flags { "OptimizeSpeed" }
if _OPTIONS["with-debug-symbols"] then
flags { "Symbols" }
end
buildoptions{ "-O3" }
targetname ( "ecode-macos-helper-static" )
end
project "ecode"
set_kind()
language "C++"
@@ -1908,7 +1885,6 @@ solution "eepp"
end
if os.is("macosx") then
links { "CoreFoundation.framework", "CoreServices.framework", "Cocoa.framework" }
links { "ecode-macos-helper-static" }
end
if os.is_real("linux") then
links { "util" }
+2 -22
View File
@@ -1674,7 +1674,8 @@ workspace "eepp"
language "C++"
cppdialect "C++20"
incdirs { "include", "src" }
files { "src/eepp/ui/platform/macos/macosmenubar.mm" }
files { "src/eepp/ui/platform/macos/macosmenubar.mm",
"src/eepp/window/platform/macos/platformhelper.mm" }
buildoptions { "-x objective-c++" }
build_base_cpp_configuration( "eepp-macos-helper" )
target_dir_lib( "" )
@@ -1901,26 +1902,6 @@ workspace "eepp"
filter { "system:not windows", "system:not haiku" }
links { "pthread" }
if os.istarget("macosx") then
project "ecode-macos-helper-static"
kind "StaticLib"
language "C++"
target_dir_thirdparty()
filter "system:macosx"
files { "src/tools/ecode/macos/*.m" }
filter { "configurations:debug*", "action:not vs*" }
defines { "DEBUG", "EE_DEBUG", "EE_MEMORY_MANAGER" }
symbols "On"
buildoptions{ "-Wall" }
targetname ( "ecode-macos-helper-static-debug" )
filter { "configurations:release*", "action:not vs*" }
defines { "NDEBUG" }
optimize "Speed"
targetname ( "ecode-macos-helper-static" )
filter { "configurations:release*", "action:not vs*", "options:with-debug-symbols" }
symbols "On"
end
project "ecode"
set_kind()
language "C++"
@@ -1943,7 +1924,6 @@ workspace "eepp"
linkoptions { _MAIN_SCRIPT_DIR .. "/bin/assets/icon/ecode.x64.res" }
filter "system:macosx"
links { "CoreFoundation.framework", "CoreServices.framework", "Cocoa.framework" }
links { "ecode-macos-helper-static" }
filter { "system:not windows", "system:not haiku" }
links { "pthread" }
filter "system:linux"
+2 -3
View File
@@ -80,8 +80,7 @@ void EventDispatcher::inputCallback( InputEvent* event ) {
break;
case InputEvent::MouseWheel:
sendMouseWheel( { event->wheel.x, event->wheel.y },
event->wheel.direction == InputEvent::WheelEvent::Normal ? true
: false );
event->wheel.direction == InputEvent::WheelEvent::Flipped );
break;
case InputEvent::SysWM:
case InputEvent::VideoResize:
@@ -288,7 +287,7 @@ void EventDispatcher::sendKeyDown( const Keycode& keyCode, const Scancode& scanc
}
void EventDispatcher::sendMouseWheel( const Vector2f& offset, bool flipped ) {
Node* node = mFocusNode;
Node* node = nullptr != mOverNode ? mOverNode : mFocusNode;
while ( NULL != node ) {
if ( node->isEnabled() && node->onMouseWheel( offset, flipped ) )
break;
+1 -1
View File
@@ -388,7 +388,7 @@ Uint32 Node::onMouseLeave( const Vector2i& Pos, const Uint32& Flags ) {
}
Uint32 Node::onMouseWheel( const Vector2f&, bool ) {
return 1;
return 0;
}
Uint32 Node::onCalculateDrag( const Vector2f&, const Uint32& ) {
@@ -422,6 +422,8 @@ void StyleSheetSpecification::registerDefaultProperties() {
registerProperty( PropertyId::TouchDrag, "touch-drag", "" ).setType( PropertyType::Bool );
registerProperty( PropertyId::TouchDragDeceleration, "touch-drag-deceleration", "" )
.setType( PropertyType::NumberFloat );
registerProperty( PropertyId::ScrollBehavior, "scroll-behavior", "instant" )
.setType( PropertyType::String );
registerProperty( PropertyId::WindowTitle, "window-title", "" ).setType( PropertyType::String );
registerProperty( PropertyId::WindowOpacity, "window-opacity", "" )
+2
View File
@@ -14,6 +14,7 @@
#include <eepp/ui/uiwidget.hpp>
#include <eepp/window/engine.hpp>
#include <eepp/window/input.hpp>
#include <eepp/window/platformhelper.hpp>
#include <eepp/window/runtime.hpp>
#include <atomic>
@@ -74,6 +75,7 @@ UIApplication::UIApplication( const WindowSettings& windowSettings, const Settin
std::cerr << "Could not create window, exiting" << std::endl;
return;
}
Engine::instance()->getPlatformHelper()->setNativeScrollMomentumEnabled( true );
mWindow->setDeferNativeResourceDestructionOnClose( true );
mDidRun = true;
+56 -20
View File
@@ -1,5 +1,6 @@
#include "eepp/ui/uistyle.hpp"
#include <algorithm>
#include <cmath>
#include <eepp/graphics/fonttruetype.hpp>
#include <eepp/graphics/globalbatchrenderer.hpp>
#include <eepp/graphics/primitives.hpp>
@@ -163,7 +164,7 @@ const MouseBindings::ShortcutMap UICodeEditor::getDefaultMousebindings() {
UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegisterBaseCommands,
const bool& autoRegisterBaseKeybindings ) :
UIWidget( elementTag ),
UITouchDraggableWidget( elementTag ),
mFont( getUISceneNode()->getResourceScope()->findFont( "monospace" ).get() ),
mDoc( std::make_shared<TextDocument>() ),
mAsyncLifetime( this, this ),
@@ -272,7 +273,7 @@ Uint32 UICodeEditor::getType() const {
}
bool UICodeEditor::isType( const Uint32& type ) const {
return type == getType() || UIWidget::isType( type );
return type == getType() || UITouchDraggableWidget::isType( type );
}
void UICodeEditor::setTheme( UITheme* Theme ) {
@@ -486,7 +487,9 @@ void UICodeEditor::draw() {
clipSmartDisable();
}
void UICodeEditor::scheduledUpdate( const Time& ) {
void UICodeEditor::scheduledUpdate( const Time& time ) {
UITouchDraggableWidget::scheduledUpdate( time );
if ( mDisableCursorBlinkingAfterAMinuteOfInactivity &&
mLastActivity.getElapsedTime() > Seconds( 60 ) ) {
if ( !mCursorVisible ) {
@@ -775,7 +778,23 @@ Uint32 UICodeEditor::onMessage( const NodeMessage* msg ) {
setFocus();
}
}
return UIWidget::onMessage( msg );
return UITouchDraggableWidget::onMessage( msg );
}
bool UICodeEditor::supportsScrollController() const {
return true;
}
Vector2f UICodeEditor::getScrollControllerPosition() const {
return mScroll;
}
Vector2f UICodeEditor::getScrollControllerMaxPosition() const {
return getMaxScroll();
}
void UICodeEditor::setScrollControllerPosition( const Vector2f& position ) {
setScroll( position );
}
void UICodeEditor::disableEditorFeatures( bool useDefaultStyle ) {
@@ -1887,23 +1906,11 @@ Uint32 UICodeEditor::onMouseUp( const Vector2i& position, const Uint32& flags )
} else if ( flags & EE_BUTTON_WDMASK ) {
if ( getInput()->isKeyModPressed() ) {
mDoc->execute( "font-size-shrink" );
} else if ( input->isModState( KEYMOD_SHIFT ) ) {
setScrollX( mScroll.x + mMouseWheelScroll );
} else {
setScrollY( mScroll.y + mMouseWheelScroll );
}
} else if ( flags & EE_BUTTON_WUMASK ) {
if ( getInput()->isKeyModPressed() ) {
mDoc->execute( "font-size-grow" );
} else if ( input->isModState( KEYMOD_SHIFT ) ) {
setScrollX( mScroll.x - mMouseWheelScroll );
} else {
setScrollY( mScroll.y - mMouseWheelScroll );
}
} else if ( flags & EE_BUTTON_WRMASK ) {
setScrollX( mScroll.x + mMouseWheelScroll );
} else if ( flags & EE_BUTTON_WLMASK ) {
setScrollX( mScroll.x - mMouseWheelScroll );
} else if ( !minimapHover && ( flags & EE_BUTTON_RMASK ) ) {
Vector2f localPos( convertToNodeSpace( position.asFloat() ) );
if ( localPos.x >= mPaddingPx.Left + getGutterWidth() && localPos.y >= mPluginsTopSpace )
@@ -1914,6 +1921,31 @@ Uint32 UICodeEditor::onMouseUp( const Vector2i& position, const Uint32& flags )
return UIWidget::onMouseUp( position, flags );
}
Uint32 UICodeEditor::onMouseWheel( const Vector2f& offset, bool ) {
Input* input = getInput();
if ( input->isKeyModPressed() )
return 1;
Vector2f delta;
Float durationScale = 1.f;
if ( input->isModState( KEYMOD_SHIFT ) && offset.y != 0.f ) {
const Float factor = getWheelScrollFactor( offset.y );
delta.x = -factor * mMouseWheelScroll;
durationScale = std::abs( factor );
} else {
if ( offset.y != 0.f ) {
const Float factor = getWheelScrollFactor( offset.y );
delta.y = -factor * mMouseWheelScroll;
durationScale = std::abs( factor );
} else if ( offset.x != 0.f ) {
const Float factor = getWheelScrollFactor( offset.x );
delta.x = factor * mMouseWheelScroll;
durationScale = std::abs( factor );
}
}
return scrollBy( delta, durationScale ) ? 1 : 0;
}
Uint32 UICodeEditor::onMouseClick( const Vector2i& position, const Uint32& flags ) {
mLastActivity.restart();
for ( auto& plugin : mPlugins )
@@ -2641,6 +2673,8 @@ void UICodeEditor::showMinimap( bool showMinimap ) {
}
bool UICodeEditor::setScrollX( const Float& val, bool emitEvent ) {
if ( !mApplyingScrollController )
stopScrollController();
Float oldVal = mScroll.x;
mScroll.x = eefloor( eeclamp<Float>( val, 0.f, getMaxScroll().x ) );
if ( oldVal != mScroll.x ) {
@@ -2656,6 +2690,8 @@ bool UICodeEditor::setScrollX( const Float& val, bool emitEvent ) {
}
bool UICodeEditor::setScrollY( const Float& val, bool emitEvent ) {
if ( !mApplyingScrollController )
stopScrollController();
Float oldVal = mScroll.y;
mScroll.y = eefloor( eeclamp<Float>( val, 0, getMaxScroll().y ) );
if ( oldVal != mScroll.y ) {
@@ -3196,7 +3232,7 @@ bool UICodeEditor::applyProperty( const StyleSheetProperty& attribute ) {
break;
}
default:
return UIWidget::applyProperty( attribute );
return UITouchDraggableWidget::applyProperty( attribute );
}
return true;
@@ -3250,12 +3286,12 @@ std::string UICodeEditor::getPropertyString( const PropertyDefinition* propertyD
case PropertyId::Text:
return mDoc->getLineTextUtf8( 0 );
default:
return UIWidget::getPropertyString( propertyDef, propertyIndex );
return UITouchDraggableWidget::getPropertyString( propertyDef, propertyIndex );
}
}
std::vector<PropertyId> UICodeEditor::getPropertiesImplemented() const {
auto props = UIWidget::getPropertiesImplemented();
auto props = UITouchDraggableWidget::getPropertiesImplemented();
auto local = { PropertyId::Locked,
PropertyId::Color,
PropertyId::TextShadowColor,
@@ -5910,7 +5946,7 @@ TextDirection UICodeEditor::getTextDirection() const {
void UICodeEditor::loadFromXmlNode( const pugi::xml_node& node ) {
beginAttributesTransaction();
UIWidget::loadFromXmlNode( node );
UITouchDraggableWidget::loadFromXmlNode( node );
std::string text;
bool hasElementChildren = false;
+28
View File
@@ -22,6 +22,7 @@
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uistyle.hpp>
#include <eepp/ui/uithememanager.hpp>
#include <eepp/ui/uitouchdraggablewidget.hpp>
#include <eepp/ui/uitooltip.hpp>
#include <eepp/ui/uiwebview.hpp>
#include <eepp/ui/uiwidgetcreator.hpp>
@@ -391,6 +392,33 @@ Uint32 UISceneNode::getDefaultTextHints() const {
return mDefaultTextHints;
}
UISceneNode* UISceneNode::setSmoothScrollEnabled( bool enabled, bool applyNow ) {
UISceneNode* rootScene = this;
while ( rootScene->mHostUISceneNode )
rootScene = rootScene->mHostUISceneNode;
rootScene->mSmoothScrollEnabled = enabled;
if ( applyNow ) {
const auto applyToScene = [enabled]( auto&& self, UISceneNode* scene ) -> void {
for ( auto* widget : scene->findAllByType<UITouchDraggableWidget>(
UI_TYPE_TOUCH_DRAGGABLE_WIDGET ) )
widget->setSmoothScrollEnabled( enabled );
for ( auto* childScene : scene->mChildUISceneNodes )
self( self, childScene );
};
applyToScene( applyToScene, rootScene );
}
return this;
}
bool UISceneNode::isSmoothScrollEnabled() const {
const UISceneNode* rootScene = this;
while ( rootScene->mHostUISceneNode )
rootScene = rootScene->mHostUISceneNode;
return rootScene->mSmoothScrollEnabled;
}
Uint32 UISceneNode::resolveTextHints( Uint32 defaultHints, Uint32 overrideValue,
Uint32 overrideMask ) {
const Uint32 featureMask = TextHints::OpenTypeFeatures;
+51 -34
View File
@@ -1,11 +1,13 @@
#include <cmath>
#include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/uiscrollablewidget.hpp>
#include <eepp/ui/uiscrollbar.hpp>
#include <eepp/window/input.hpp>
namespace EE { namespace UI {
UIScrollableWidget::UIScrollableWidget( const std::string& tag ) :
UIWidget( tag ),
UITouchDraggableWidget( tag ),
mScrollViewType( ScrollViewType::Outside ),
mVScrollMode( ScrollBarMode::Auto ),
mHScrollMode( ScrollBarMode::Auto ),
@@ -29,7 +31,7 @@ Uint32 UIScrollableWidget::getType() const {
}
bool UIScrollableWidget::isType( const Uint32& type ) const {
return UIWidget::getType() == type ? true : UIWidget::isType( type );
return UIScrollableWidget::getType() == type ? true : UITouchDraggableWidget::isType( type );
}
void UIScrollableWidget::onSizeChange() {
@@ -239,6 +241,8 @@ void UIScrollableWidget::onScrollChange() {}
void UIScrollableWidget::onValueChangeCb( const Event* ) {
updateScroll();
if ( !mApplyingScrollController )
stopScrollController();
}
std::string UIScrollableWidget::getPropertyString( const PropertyDefinition* propertyDef,
@@ -261,12 +265,12 @@ std::string UIScrollableWidget::getPropertyString( const PropertyDefinition* pro
case PropertyId::ScrollBarMode:
return getViewType() == ScrollViewType::Overlay ? "overlay" : "outside";
default:
return UIWidget::getPropertyString( propertyDef, propertyIndex );
return UITouchDraggableWidget::getPropertyString( propertyDef, propertyIndex );
}
}
std::vector<PropertyId> UIScrollableWidget::getPropertiesImplemented() const {
auto props = UIWidget::getPropertiesImplemented();
auto props = UITouchDraggableWidget::getPropertiesImplemented();
auto local = { PropertyId::VScrollMode, PropertyId::HScrollMode, PropertyId::ScrollBarStyle,
PropertyId::ScrollBarMode };
props.insert( props.end(), local.begin(), local.end() );
@@ -358,7 +362,7 @@ bool UIScrollableWidget::applyProperty( const StyleSheetProperty& attribute ) {
break;
}
default:
return UIWidget::applyProperty( attribute );
return UITouchDraggableWidget::applyProperty( attribute );
}
return true;
@@ -366,34 +370,6 @@ bool UIScrollableWidget::applyProperty( const StyleSheetProperty& attribute ) {
Uint32 UIScrollableWidget::onMessage( const NodeMessage* Msg ) {
switch ( Msg->getMsg() ) {
case NodeMessage::MouseUp: {
bool moved = false;
if ( mVScroll->isEnabled() ) {
if ( Msg->getFlags() & EE_BUTTON_WUMASK ) {
mVScroll->setValue( mVScroll->getValue() - mVScroll->getClickStep() );
moved = true;
} else if ( Msg->getFlags() & EE_BUTTON_WDMASK ) {
mVScroll->setValue( mVScroll->getValue() + mVScroll->getClickStep() );
moved = true;
}
}
if ( mHScroll->isEnabled() ) {
if ( Msg->getFlags() & EE_BUTTON_WLMASK ) {
mHScroll->setValue( mHScroll->getValue() - mHScroll->getClickStep() );
moved = true;
} else if ( Msg->getFlags() & EE_BUTTON_WRMASK ) {
mHScroll->setValue( mHScroll->getValue() + mHScroll->getClickStep() );
moved = true;
}
}
if ( moved )
return 1;
break;
}
case NodeMessage::FocusLoss: {
if ( NULL != getEventDispatcher() ) {
Node* focusNode = getEventDispatcher()->getFocusNode();
@@ -408,7 +384,48 @@ Uint32 UIScrollableWidget::onMessage( const NodeMessage* Msg ) {
break;
}
}
return UIWidget::onMessage( Msg );
return UITouchDraggableWidget::onMessage( Msg );
}
Uint32 UIScrollableWidget::onMouseWheel( const Vector2f& offset, bool ) {
const Vector2f maxPosition( getScrollControllerMaxPosition() );
Vector2f delta;
Float durationScale = 1.f;
if ( getInput()->isModState( KEYMOD_SHIFT ) && offset.y != 0.f && mHScroll->isEnabled() ) {
const Float factor = getWheelScrollFactor( offset.y );
delta.x = -factor * mHScroll->getClickStep() * maxPosition.x;
durationScale = std::abs( factor );
} else {
if ( offset.y != 0.f && mVScroll->isEnabled() ) {
const Float factor = getWheelScrollFactor( offset.y );
delta.y = -factor * mVScroll->getClickStep() * maxPosition.y;
durationScale = std::abs( factor );
} else if ( offset.x != 0.f && mHScroll->isEnabled() ) {
const Float factor = getWheelScrollFactor( offset.x );
delta.x = factor * mHScroll->getClickStep() * maxPosition.x;
durationScale = std::abs( factor );
}
}
return scrollBy( delta, durationScale ) ? 1 : 0;
}
bool UIScrollableWidget::supportsScrollController() const {
return true;
}
Vector2f UIScrollableWidget::getScrollControllerPosition() const {
return mScrollOffset;
}
Vector2f UIScrollableWidget::getScrollControllerMaxPosition() const {
const Sizef area( getScrollableArea() );
return { eemax( 0.f, area.x ), eemax( 0.f, area.y ) };
}
void UIScrollableWidget::setScrollControllerPosition( const Vector2f& position ) {
const Vector2f maxPosition( getScrollControllerMaxPosition() );
mHScroll->setValue( maxPosition.x > 0.f ? position.x / maxPosition.x : 0.f );
mVScroll->setValue( maxPosition.y > 0.f ? position.y / maxPosition.y : 0.f );
}
}} // namespace EE::UI
+210
View File
@@ -0,0 +1,210 @@
#include <cmath>
#include <eepp/ui/uiscrollcontroller.hpp>
namespace EE { namespace UI {
static Vector2f clampScrollPosition( const Vector2f& position, const Vector2f& maxPosition ) {
return { eeclamp( position.x, 0.f, eemax( 0.f, maxPosition.x ) ),
eeclamp( position.y, 0.f, eemax( 0.f, maxPosition.y ) ) };
}
static bool isNearlyZero( Float value, Float epsilon ) {
return std::abs( value ) <= epsilon;
}
UIScrollController::UIScrollController() = default;
void UIScrollController::setPosition( const Vector2f& position, const Vector2f& maxPosition ) {
mPosition = clampScrollPosition( position, maxPosition );
mTarget = mPosition;
mVelocity = Vector2f::Zero;
mSmoothTweenCount = 0;
mMode = Mode::Idle;
}
bool UIScrollController::scrollBy( const Vector2f& delta, const Vector2f& currentPosition,
const Vector2f& maxPosition, bool smooth, Float durationScale ) {
if ( mMode != Mode::Smooth ) {
mPosition = clampScrollPosition( currentPosition, maxPosition );
mTarget = mPosition;
}
const Vector2f oldTarget( mTarget );
mTarget = clampScrollPosition( mTarget + delta, maxPosition );
mVelocity = Vector2f::Zero;
if ( oldTarget == mTarget )
return false;
if ( smooth ) {
const Vector2f effectiveDelta( mTarget - oldTarget );
const Float requestedDistance = std::sqrt( delta.x * delta.x + delta.y * delta.y );
const Float effectiveDistance =
std::sqrt( effectiveDelta.x * effectiveDelta.x + effectiveDelta.y * effectiveDelta.y );
const Float duration =
mWheelScrollDuration.asSeconds() * eeclamp( durationScale, 0.f, 1.f ) *
( requestedDistance > 0.f ? effectiveDistance / requestedDistance : 1.f );
if ( mSmoothTweenCount > 0 && mSmoothTweens[mSmoothTweenCount - 1].elapsed == 0.f &&
mSmoothTweens[mSmoothTweenCount - 1].duration == duration ) {
mSmoothTweens[mSmoothTweenCount - 1].delta += effectiveDelta;
} else if ( mSmoothTweenCount < mSmoothTweens.size() ) {
mSmoothTweens[mSmoothTweenCount++] = { effectiveDelta, 0.f, duration };
} else {
mSmoothTweens[mSmoothTweenCount - 1].delta += effectiveDelta;
}
mMode = Mode::Smooth;
} else {
mPosition = mTarget;
mSmoothTweenCount = 0;
mMode = Mode::Idle;
}
return true;
}
void UIScrollController::beginDrag( const Vector2f& position, const Vector2f& maxPosition ) {
mPosition = clampScrollPosition( position, maxPosition );
mTarget = mPosition;
mVelocity = Vector2f::Zero;
mSmoothTweenCount = 0;
mMode = Mode::Dragging;
}
bool UIScrollController::dragBy( const Vector2f& delta, const Time& elapsed,
const Vector2f& maxPosition ) {
if ( mMode != Mode::Dragging )
beginDrag( mPosition, maxPosition );
const Vector2f oldPosition( mPosition );
mPosition = clampScrollPosition( mPosition + delta, maxPosition );
mTarget = mPosition;
const Float seconds = eemax<Float>( 0.f, elapsed.asSeconds() );
if ( seconds > 0.f ) {
const Vector2f instantaneousVelocity( ( mPosition - oldPosition ) / seconds );
const Float blend = 1.f - std::exp( -20.f * seconds );
mVelocity += ( instantaneousVelocity - mVelocity ) * blend;
}
return oldPosition != mPosition;
}
void UIScrollController::endDrag() {
if ( mMode != Mode::Dragging )
return;
if ( isNearlyZero( mVelocity.x, 1.f ) && isNearlyZero( mVelocity.y, 1.f ) ) {
stop();
} else {
mMode = Mode::Momentum;
}
}
bool UIScrollController::update( const Time& elapsed, const Vector2f& maxPosition ) {
if ( mMode == Mode::Idle || mMode == Mode::Dragging )
return false;
const Float seconds = eemax<Float>( 0.f, elapsed.asSeconds() );
if ( seconds <= 0.f )
return false;
const Vector2f oldPosition( mPosition );
if ( mMode == Mode::Smooth ) {
mTarget = clampScrollPosition( mTarget, maxPosition );
Uint32 activeTweens = 0;
for ( Uint32 i = 0; i < mSmoothTweenCount; ++i ) {
auto& tween = mSmoothTweens[i];
const Float oldProgress =
tween.duration > 0.f ? eeclamp( tween.elapsed / tween.duration, 0.f, 1.f ) : 1.f;
tween.elapsed += seconds;
const Float progress =
tween.duration > 0.f ? eeclamp( tween.elapsed / tween.duration, 0.f, 1.f ) : 1.f;
const Float oldEased = 1.f - ( 1.f - oldProgress ) * ( 1.f - oldProgress );
const Float eased = 1.f - ( 1.f - progress ) * ( 1.f - progress );
mPosition += tween.delta * ( eased - oldEased );
if ( progress < 1.f )
mSmoothTweens[activeTweens++] = tween;
}
mSmoothTweenCount = activeTweens;
mPosition = clampScrollPosition( mPosition, maxPosition );
if ( mSmoothTweenCount == 0 ) {
mPosition = mTarget;
mMode = Mode::Idle;
}
} else {
const Vector2f decay( std::exp( -mDragDeceleration.x * seconds ),
std::exp( -mDragDeceleration.y * seconds ) );
const Vector2f displacement( mVelocity.x * ( 1.f - decay.x ) / mDragDeceleration.x,
mVelocity.y * ( 1.f - decay.y ) / mDragDeceleration.y );
const Vector2f unclampedPosition( mPosition + displacement );
mPosition = clampScrollPosition( unclampedPosition, maxPosition );
mTarget = mPosition;
if ( mPosition.x != unclampedPosition.x )
mVelocity.x = 0.f;
else
mVelocity.x *= decay.x;
if ( mPosition.y != unclampedPosition.y )
mVelocity.y = 0.f;
else
mVelocity.y *= decay.y;
if ( isNearlyZero( mVelocity.x, 1.f ) )
mVelocity.x = 0.f;
if ( isNearlyZero( mVelocity.y, 1.f ) )
mVelocity.y = 0.f;
if ( mVelocity == Vector2f::Zero )
mMode = Mode::Idle;
}
return oldPosition != mPosition;
}
void UIScrollController::stop() {
mTarget = mPosition;
mVelocity = Vector2f::Zero;
mSmoothTweenCount = 0;
mMode = Mode::Idle;
}
bool UIScrollController::isActive() const {
return mMode != Mode::Idle;
}
bool UIScrollController::isDragging() const {
return mMode == Mode::Dragging;
}
const Vector2f& UIScrollController::getPosition() const {
return mPosition;
}
const Vector2f& UIScrollController::getTarget() const {
return mTarget;
}
const Vector2f& UIScrollController::getVelocity() const {
return mVelocity;
}
Time UIScrollController::getWheelScrollDuration() const {
return mWheelScrollDuration;
}
void UIScrollController::setWheelScrollDuration( const Time& duration ) {
mWheelScrollDuration = Seconds( eemax( 0.001f, static_cast<Float>( duration.asSeconds() ) ) );
}
Vector2f UIScrollController::getDragDeceleration() const {
return mDragDeceleration;
}
void UIScrollController::setDragDeceleration( const Vector2f& deceleration ) {
mDragDeceleration = { eemax( 0.01f, deceleration.x ), eemax( 0.01f, deceleration.y ) };
}
}} // namespace EE::UI
+40 -15
View File
@@ -1,6 +1,8 @@
#include <cmath>
#include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/uiscrollbar.hpp>
#include <eepp/ui/uiscrollview.hpp>
#include <eepp/window/input.hpp>
namespace EE { namespace UI {
@@ -302,6 +304,8 @@ void UIScrollView::updateScroll() {
void UIScrollView::onValueChangeCb( const Event* ) {
updateScroll();
if ( !mApplyingScrollController )
stopScrollController();
}
void UIScrollView::onScrollViewSizeChange( const Event* ) {
@@ -445,24 +449,45 @@ bool UIScrollView::applyProperty( const StyleSheetProperty& attribute ) {
}
Uint32 UIScrollView::onMessage( const NodeMessage* Msg ) {
switch ( Msg->getMsg() ) {
case NodeMessage::MouseUp: {
if ( mScrollView && mVScroll->isEnabled() && 0 != mScrollView->getSize().getHeight() &&
isTouchOverAllowedChildren() && Msg->getSender()->isUINode() &&
!Msg->getSender()->asType<UINode>()->isScrollable() ) {
if ( Msg->getFlags() & EE_BUTTON_WUMASK ) {
mVScroll->setValue( mVScroll->getValue() - mVScroll->getClickStep() );
return 1;
} else if ( Msg->getFlags() & EE_BUTTON_WDMASK ) {
mVScroll->setValue( mVScroll->getValue() + mVScroll->getClickStep() );
return 1;
}
}
}
}
return UITouchDraggableWidget::onMessage( Msg );
}
Uint32 UIScrollView::onMouseWheel( const Vector2f& offset, bool ) {
if ( !mScrollView || !mVScroll->isEnabled() || !isTouchOverAllowedChildren() )
return 0;
if ( offset.y == 0.f )
return 0;
const Vector2f maxPosition( getScrollControllerMaxPosition() );
const Float factor = getWheelScrollFactor( offset.y );
const Float delta = -factor * mVScroll->getClickStep() * maxPosition.y;
return scrollBy( { 0.f, delta }, std::abs( factor ) ) ? 1 : 0;
}
bool UIScrollView::supportsScrollController() const {
return true;
}
Vector2f UIScrollView::getScrollControllerPosition() const {
const Vector2f maxPosition( getScrollControllerMaxPosition() );
return { mHScroll->isEnabled() ? mHScroll->getValue() * maxPosition.x : 0.f,
mVScroll->isEnabled() ? mVScroll->getValue() * maxPosition.y : 0.f };
}
Vector2f UIScrollView::getScrollControllerMaxPosition() const {
if ( !mScrollView )
return Vector2f::Zero;
return { eemax( 0.f, mScrollView->getPixelsSize().x - mContainer->getPixelsSize().x ),
eemax( 0.f, mScrollView->getPixelsSize().y - mContainer->getPixelsSize().y ) };
}
void UIScrollView::setScrollControllerPosition( const Vector2f& position ) {
const Vector2f maxPosition( getScrollControllerMaxPosition() );
mHScroll->setValue( maxPosition.x > 0.f ? position.x / maxPosition.x : 0.f );
mVScroll->setValue( maxPosition.y > 0.f ? position.y / maxPosition.y : 0.f );
}
bool UIScrollView::isAutoSetClipStep() const {
return mAutoSetClipStep;
}
+135 -51
View File
@@ -1,5 +1,7 @@
#include <cmath>
#include <eepp/scene/scenenode.hpp>
#include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uitouchdraggablewidget.hpp>
namespace EE { namespace UI {
@@ -10,6 +12,9 @@ UITouchDraggableWidget* UITouchDraggableWidget::New() {
UITouchDraggableWidget::UITouchDraggableWidget( const std::string& tag ) :
UIWidget( tag ), mTouchDragDeceleration( 5.f, 5.f ) {
mScrollController.setDragDeceleration( mTouchDragDeceleration );
if ( getUISceneNode() )
mSmoothScrollEnabled = getUISceneNode()->isSmoothScrollEnabled();
subscribeScheduledUpdate();
}
@@ -33,6 +38,12 @@ bool UITouchDraggableWidget::isTouchDragEnabled() const {
}
UITouchDraggableWidget* UITouchDraggableWidget::setTouchDragEnabled( const bool& enable ) {
if ( !enable && isTouchDragging() ) {
setTouchDragging( false );
if ( nullptr != getEventDispatcher() && getEventDispatcher()->getNodeDragging() == this )
getEventDispatcher()->setNodeDragging( nullptr );
stopScrollController();
}
writeFlag( UI_TOUCH_DRAG_ENABLED, true == enable );
return this;
}
@@ -52,7 +63,21 @@ Vector2f UITouchDraggableWidget::getTouchDragDeceleration() const {
UITouchDraggableWidget*
UITouchDraggableWidget::setTouchDragDeceleration( const Vector2f& touchDragDeceleration ) {
mTouchDragDeceleration = touchDragDeceleration;
mTouchDragDeceleration = { eemax( 0.01f, touchDragDeceleration.x ),
eemax( 0.01f, touchDragDeceleration.y ) };
mScrollController.setDragDeceleration( mTouchDragDeceleration );
return this;
}
bool UITouchDraggableWidget::isSmoothScrollEnabled() const {
return mSmoothScrollEnabled;
}
UITouchDraggableWidget* UITouchDraggableWidget::setSmoothScrollEnabled( bool enabled ) {
if ( mSmoothScrollEnabled != enabled ) {
mSmoothScrollEnabled = enabled;
stopScrollController();
}
return this;
}
@@ -62,70 +87,118 @@ bool UITouchDraggableWidget::isTouchOverAllowedChildren() {
return isMouseOverMeOrChildren();
}
bool UITouchDraggableWidget::supportsScrollController() const {
return false;
}
Vector2f UITouchDraggableWidget::getScrollControllerPosition() const {
return Vector2f::Zero;
}
Vector2f UITouchDraggableWidget::getScrollControllerMaxPosition() const {
return Vector2f::Zero;
}
void UITouchDraggableWidget::setScrollControllerPosition( const Vector2f& ) {}
bool UITouchDraggableWidget::scrollBy( const Vector2f& delta, Float durationScale ) {
if ( !supportsScrollController() )
return false;
const bool moved = mScrollController.scrollBy( delta, getScrollControllerPosition(),
getScrollControllerMaxPosition(),
mSmoothScrollEnabled, durationScale );
if ( moved && !mSmoothScrollEnabled ) {
mApplyingScrollController = true;
setScrollControllerPosition( mScrollController.getPosition() );
mApplyingScrollController = false;
}
return moved;
}
Float UITouchDraggableWidget::getWheelScrollFactor( Float offset ) {
return eeclamp( offset, -1.f, 1.f );
}
void UITouchDraggableWidget::stopScrollController() {
if ( supportsScrollController() ) {
mScrollController.setPosition( getScrollControllerPosition(),
getScrollControllerMaxPosition() );
} else {
mScrollController.stop();
mTouchDragVelocity = Vector2f::Zero;
}
}
void UITouchDraggableWidget::scheduledUpdate( const Time& time ) {
if ( mEnabled && mVisible && isTouchDragEnabled() && NULL != getEventDispatcher() ) {
if ( !mEnabled || !mVisible || NULL == getEventDispatcher() )
return;
if ( isTouchDragEnabled() ) {
if ( isTouchDragging() ) {
// Mouse Not Down
if ( !( getEventDispatcher()->getPressTrigger() & EE_BUTTON_LMASK ) ) {
setTouchDragging( false );
getEventDispatcher()->setNodeDragging( NULL );
return;
if ( supportsScrollController() )
mScrollController.endDrag();
}
Float ms = time.asSeconds();
Vector2f elapsed( ms, ms );
Vector2f Pos( getEventDispatcher()->getMousePosf() );
if ( isTouchDragging() ) {
const Float seconds = eemax<Float>( 0.f, time.asSeconds() );
Vector2f pos( getEventDispatcher()->getMousePosf() );
if ( mTouchDragPoint != Pos ) {
Vector2f diff = -( mTouchDragPoint - Pos );
if ( mTouchDragPoint != pos ) {
Vector2f diff( pos - mTouchDragPoint );
onTouchDragValueChange( diff );
if ( supportsScrollController() ) {
mScrollController.dragBy( -diff, time, getScrollControllerMaxPosition() );
mApplyingScrollController = true;
setScrollControllerPosition( mScrollController.getPosition() );
mApplyingScrollController = false;
} else {
onTouchDragValueChange( diff );
if ( seconds > 0.f ) {
const Vector2f velocity( diff / seconds );
const Float blend = 1.f - std::exp( -20.f * seconds );
mTouchDragVelocity += ( velocity - mTouchDragVelocity ) * blend;
}
}
mTouchDragAcceleration += elapsed * diff;
mTouchDragPoint = pos;
mTouchDragPoint = Pos;
getEventDispatcher()->setNodeDragging( this );
} else if ( mTouchDragAcceleration != Vector2f::Zero ) {
mTouchDragAcceleration -= elapsed * mTouchDragDeceleration;
}
} else {
// Deaccelerate
if ( mTouchDragAcceleration.x != 0 || mTouchDragAcceleration.y != 0 ) {
Float ms = getEventDispatcher()->getLastFrameTime().asSeconds();
if ( 0 != mTouchDragAcceleration.x ) {
bool wasPositiveX = mTouchDragAcceleration.x >= 0;
if ( mTouchDragAcceleration.x > 0 )
mTouchDragAcceleration.x -= mTouchDragDeceleration.x * ms;
else
mTouchDragAcceleration.x += mTouchDragDeceleration.x * ms;
if ( wasPositiveX && mTouchDragAcceleration.x < 0 )
mTouchDragAcceleration.x = 0;
else if ( !wasPositiveX && mTouchDragAcceleration.x > 0 )
mTouchDragAcceleration.x = 0;
getEventDispatcher()->setNodeDragging( this );
} else if ( supportsScrollController() ) {
mScrollController.dragBy( Vector2f::Zero, time,
getScrollControllerMaxPosition() );
} else if ( mTouchDragVelocity != Vector2f::Zero ) {
mTouchDragVelocity *= std::exp( -20.f * seconds );
}
if ( 0 != mTouchDragAcceleration.y ) {
bool wasPositiveY = mTouchDragAcceleration.y >= 0;
if ( mTouchDragAcceleration.y > 0 )
mTouchDragAcceleration.y -= mTouchDragDeceleration.y * ms;
else
mTouchDragAcceleration.y += mTouchDragDeceleration.y * ms;
if ( wasPositiveY && mTouchDragAcceleration.y < 0 )
mTouchDragAcceleration.y = 0;
else if ( !wasPositiveY && mTouchDragAcceleration.y > 0 )
mTouchDragAcceleration.y = 0;
}
onTouchDragValueChange( mTouchDragAcceleration );
}
}
}
if ( !isTouchDragging() ) {
if ( supportsScrollController() &&
mScrollController.update( time, getScrollControllerMaxPosition() ) ) {
mApplyingScrollController = true;
setScrollControllerPosition( mScrollController.getPosition() );
mApplyingScrollController = false;
} else if ( !supportsScrollController() && mTouchDragVelocity != Vector2f::Zero ) {
const Float seconds = eemax<Float>( 0.f, time.asSeconds() );
const Vector2f decay( std::exp( -mTouchDragDeceleration.x * seconds ),
std::exp( -mTouchDragDeceleration.y * seconds ) );
onTouchDragValueChange(
{ mTouchDragVelocity.x * ( 1.f - decay.x ) / mTouchDragDeceleration.x,
mTouchDragVelocity.y * ( 1.f - decay.y ) / mTouchDragDeceleration.y } );
mTouchDragVelocity.x *= decay.x;
mTouchDragVelocity.y *= decay.y;
if ( std::abs( mTouchDragVelocity.x ) <= 1.f )
mTouchDragVelocity.x = 0.f;
if ( std::abs( mTouchDragVelocity.y ) <= 1.f )
mTouchDragVelocity.y = 0.f;
}
}
}
Uint32 UITouchDraggableWidget::onMessage( const NodeMessage* msg ) {
@@ -135,12 +208,17 @@ Uint32 UITouchDraggableWidget::onMessage( const NodeMessage* msg ) {
setTouchDragging( true );
getEventDispatcher()->setNodeDragging( this );
mTouchDragPoint = getEventDispatcher()->getMousePosf();
mTouchDragAcceleration = Vector2f( 0, 0 );
mTouchDragVelocity = Vector2f::Zero;
if ( supportsScrollController() )
mScrollController.beginDrag( getScrollControllerPosition(),
getScrollControllerMaxPosition() );
return 1;
} else if ( msg->getMsg() == NodeMessage::MouseUp && ( msg->getFlags() & EE_BUTTON_LMASK ) &&
isTouchDragging() && isTouchOverAllowedChildren() ) {
setTouchDragging( false );
getEventDispatcher()->setNodeDragging( nullptr );
if ( supportsScrollController() )
mScrollController.endDrag();
return 1;
}
return 0;
@@ -157,6 +235,8 @@ std::string UITouchDraggableWidget::getPropertyString( const PropertyDefinition*
case PropertyId::TouchDragDeceleration:
return String::fromFloat( getTouchDragDeceleration().x ) + " " +
String::fromFloat( getTouchDragDeceleration().y );
case PropertyId::ScrollBehavior:
return isSmoothScrollEnabled() ? "smooth" : "instant";
default:
return UIWidget::getPropertyString( propertyDef, propertyIndex );
}
@@ -164,7 +244,8 @@ std::string UITouchDraggableWidget::getPropertyString( const PropertyDefinition*
std::vector<PropertyId> UITouchDraggableWidget::getPropertiesImplemented() const {
auto props = UIWidget::getPropertiesImplemented();
auto local = { PropertyId::TouchDrag, PropertyId::TouchDragDeceleration };
auto local = { PropertyId::TouchDrag, PropertyId::TouchDragDeceleration,
PropertyId::ScrollBehavior };
props.insert( props.end(), local.begin(), local.end() );
return props;
}
@@ -180,6 +261,9 @@ bool UITouchDraggableWidget::applyProperty( const StyleSheetProperty& attribute
case PropertyId::TouchDragDeceleration:
setTouchDragDeceleration( attribute.asVector2f() );
break;
case PropertyId::ScrollBehavior:
setSmoothScrollEnabled( String::iequals( attribute.getValue(), "smooth" ) );
break;
default:
return UIWidget::applyProperty( attribute );
}
+12 -5
View File
@@ -301,17 +301,24 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) {
Uint8 button;
float x = SDLEvent.wheel.x;
float y = SDLEvent.wheel.y;
#if SDL_VERSION_ATLEAST( 3, 2, 12 )
const Sint32 integerX = SDLEvent.wheel.integer_x;
const Sint32 integerY = SDLEvent.wheel.integer_y;
#else
const Sint32 integerX = x > 0.f ? 1 : ( x < 0.f ? -1 : 0 );
const Sint32 integerY = y > 0.f ? 1 : ( y < 0.f ? -1 : 0 );
#endif
if ( y == 0 && x == 0 )
if ( integerY == 0 && integerX == 0 )
break;
if ( y > 0 ) {
if ( integerY > 0 ) {
button = EE_BUTTON_WHEELUP;
} else if ( y < 0 ) {
} else if ( integerY < 0 ) {
button = EE_BUTTON_WHEELDOWN;
} else if ( x > 0 ) {
} else if ( integerX > 0 ) {
button = EE_BUTTON_WHEELRIGHT;
} else if ( x < 0 ) {
} else if ( integerX < 0 ) {
button = EE_BUTTON_WHEELLEFT;
} else {
return;
@@ -0,0 +1,16 @@
#ifndef EE_WINDOW_PLATFORM_MACOS_PLATFORMHELPER_HPP
#define EE_WINDOW_PLATFORM_MACOS_PLATFORMHELPER_HPP
#include <eepp/config.hpp>
namespace EE { namespace Window { namespace Private {
bool setNativeScrollMomentumEnabled( bool enabled );
bool setWindowTitleBarSeparatorVisible( void* window, bool visible );
bool setWindowTitleBarColor( void* window, Uint8 red, Uint8 green, Uint8 blue );
}}} // namespace EE::Window::Private
#endif // EE_WINDOW_PLATFORM_MACOS_PLATFORMHELPER_HPP
@@ -0,0 +1,38 @@
#import <AppKit/AppKit.h>
#include <eepp/window/platform/macos/platformhelper.hpp>
namespace EE { namespace Window { namespace Private {
bool setNativeScrollMomentumEnabled( bool enabled ) {
[[NSUserDefaults standardUserDefaults] setBool:enabled ? YES : NO
forKey:@"AppleMomentumScrollSupported"];
return true;
}
bool setWindowTitleBarSeparatorVisible( void* window, bool visible ) {
if ( nullptr == window )
return false;
if ( @available( macOS 11.0, * ) ) {
NSWindow* nsWindow = static_cast<NSWindow*>( window );
[nsWindow setTitlebarSeparatorStyle:visible ? NSTitlebarSeparatorStyleAutomatic
: NSTitlebarSeparatorStyleNone];
return true;
}
return false;
}
bool setWindowTitleBarColor( void* window, Uint8 red, Uint8 green, Uint8 blue ) {
if ( nullptr == window )
return false;
NSWindow* nsWindow = static_cast<NSWindow*>( window );
nsWindow.titlebarAppearsTransparent = YES;
nsWindow.backgroundColor = [NSColor colorWithRed:static_cast<CGFloat>( red ) / 255.f
green:static_cast<CGFloat>( green ) / 255.f
blue:static_cast<CGFloat>( blue ) / 255.f
alpha:1.f];
return true;
}
}}} // namespace EE::Window::Private
+40
View File
@@ -0,0 +1,40 @@
#include <eepp/window/platformhelper.hpp>
#if EE_PLATFORM == EE_PLATFORM_MACOS
#include <eepp/window/platform/macos/platformhelper.hpp>
#endif
namespace EE { namespace Window {
bool PlatformHelper::setNativeScrollMomentumEnabled( bool enabled ) {
#if EE_PLATFORM == EE_PLATFORM_MACOS
return Private::setNativeScrollMomentumEnabled( enabled );
#else
(void)enabled;
return false;
#endif
}
bool PlatformHelper::setWindowTitleBarSeparatorVisible( void* window, bool visible ) {
#if EE_PLATFORM == EE_PLATFORM_MACOS
return Private::setWindowTitleBarSeparatorVisible( window, visible );
#else
(void)window;
(void)visible;
return false;
#endif
}
bool PlatformHelper::setWindowTitleBarColor( void* window, Uint8 red, Uint8 green, Uint8 blue ) {
#if EE_PLATFORM == EE_PLATFORM_MACOS
return Private::setWindowTitleBarColor( window, red, green, blue );
#else
(void)window;
(void)red;
(void)green;
(void)blue;
return false;
#endif
}
}} // namespace EE::Window
@@ -4,7 +4,7 @@
#include <eepp/ui/keyboardshortcut.hpp>
#include <eepp/ui/uipopupmenu.hpp>
#include <eepp/ui/uiscrollbar.hpp>
#include <eepp/ui/uiwidget.hpp>
#include <eepp/ui/uitouchdraggablewidget.hpp>
#include <eterm/terminal/terminaldisplay.hpp>
using namespace EE::UI;
@@ -14,7 +14,7 @@ namespace eterm { namespace UI {
class UITerminalFind;
class UITerminal : public UIWidget {
class UITerminal : public UITouchDraggableWidget {
public:
static UITerminal* New( Font* font, const Float& fontSize, const Sizef& pixelsSize,
const std::string& program = "",
@@ -157,6 +157,8 @@ class UITerminal : public UIWidget {
virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
virtual Uint32 onMouseWheel( const Vector2f& offset, bool flipped );
virtual void onPositionChange();
virtual void onSizeChange();
@@ -192,6 +194,14 @@ class UITerminal : public UIWidget {
virtual void onScrollChange();
virtual bool supportsScrollController() const;
virtual Vector2f getScrollControllerPosition() const;
virtual Vector2f getScrollControllerMaxPosition() const;
virtual void setScrollControllerPosition( const Vector2f& position );
void registerNewTerminal();
};
+60 -6
View File
@@ -1,3 +1,4 @@
#include <cmath>
#include <eepp/graphics/fonttruetype.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/luapattern.hpp>
@@ -42,7 +43,7 @@ Uint32 UITerminal::getType() const {
}
bool UITerminal::isType( const Uint32& type ) const {
return getType() == type || UIWidget::isType( type );
return getType() == type || UITouchDraggableWidget::isType( type );
}
void UITerminal::draw() {
@@ -81,7 +82,7 @@ void UITerminal::registerNewTerminal() {
}
UITerminal::UITerminal( const std::shared_ptr<TerminalDisplay>& terminalDisplay ) :
UIWidget( "terminal" ),
UITouchDraggableWidget( "terminal" ),
mKeyBindings( getInput() ),
mVScroll( UIScrollBar::NewVertical() ),
mTerm( terminalDisplay ) {
@@ -265,12 +266,12 @@ std::string UITerminal::getPropertyString( const PropertyDefinition* propertyDef
case PropertyId::ScrollBarMode:
return getScrollViewType() == ScrollViewType::Overlay ? "overlay" : "outside";
default:
return UIWidget::getPropertyString( propertyDef, propertyIndex );
return UITouchDraggableWidget::getPropertyString( propertyDef, propertyIndex );
}
}
std::vector<PropertyId> UITerminal::getPropertiesImplemented() const {
auto props = UIWidget::getPropertiesImplemented();
auto props = UITouchDraggableWidget::getPropertiesImplemented();
auto local = { PropertyId::VScrollMode, PropertyId::ScrollBarStyle, PropertyId::ScrollBarMode };
props.insert( props.end(), local.begin(), local.end() );
return props;
@@ -332,7 +333,7 @@ bool UITerminal::applyProperty( const StyleSheetProperty& attribute ) {
break;
}
default:
return UIWidget::applyProperty( attribute );
return UITouchDraggableWidget::applyProperty( attribute );
}
return true;
@@ -342,7 +343,8 @@ const std::shared_ptr<TerminalDisplay>& UITerminal::getTerm() const {
return mTerm;
}
void UITerminal::scheduledUpdate( const Time& ) {
void UITerminal::scheduledUpdate( const Time& time ) {
UITouchDraggableWidget::scheduledUpdate( time );
if ( !mTerm )
return;
auto terminal = mTerm;
@@ -558,10 +560,62 @@ Uint32 UITerminal::onMouseUp( const Vector2i& position, const Uint32& flags ) {
onCreateContextMenu( position, flags );
return 1;
}
const Uint32 modifiers = getInput()->getSanitizedModState();
if ( ( modifiers == 0 || modifiers == KEYMOD_SHIFT ) &&
( flags & ( EE_BUTTON_WUMASK | EE_BUTTON_WDMASK | EE_BUTTON_WLMASK | EE_BUTTON_WRMASK ) ) )
return 1;
mTerm->onMouseUp( position, flags );
return 1;
}
Uint32 UITerminal::onMouseWheel( const Vector2f& offset, bool ) {
const Uint32 modifiers = getInput()->getSanitizedModState();
if ( modifiers != 0 && modifiers != KEYMOD_SHIFT )
return 1;
if ( offset.y == 0.f )
return 0;
const Float rows = modifiers == KEYMOD_SHIFT ? getVisibleArea() : 1.f;
const Float multiplier = rows * mTerm->getLineHeight();
const Float factor = getWheelScrollFactor( offset.y );
return scrollBy( { 0.f, -factor * multiplier }, std::abs( factor ) ) ? 1 : 0;
}
bool UITerminal::supportsScrollController() const {
return true;
}
Vector2f UITerminal::getScrollControllerPosition() const {
const int scrollableArea = eemax( 0, getScrollableArea() );
const Float lineHeight = mTerm ? mTerm->getLineHeight() : 0.f;
return { 0.f, mTerm ? eeclamp( static_cast<Float>( scrollableArea ) -
static_cast<Float>( mTerm->scrollPosition() ),
0.f, static_cast<Float>( scrollableArea ) ) *
lineHeight
: 0.f };
}
Vector2f UITerminal::getScrollControllerMaxPosition() const {
return { 0.f, static_cast<Float>( eemax( 0, getScrollableArea() ) ) *
( mTerm ? mTerm->getLineHeight() : 0.f ) };
}
void UITerminal::setScrollControllerPosition( const Vector2f& position ) {
const int scrollableArea = getScrollableArea();
const Float lineHeight = mTerm ? mTerm->getLineHeight() : 0.f;
if ( scrollableArea <= 0 || lineHeight <= 0.f )
return;
const int scrollOffset = static_cast<int>(
eefloor( eeclamp( position.y / lineHeight, 0.f, static_cast<Float>( scrollableArea ) ) ) );
if ( scrollOffset == mScrollOffset )
return;
mScrollOffset = scrollOffset;
mVScroll->setValue( static_cast<Float>( mScrollOffset ) / scrollableArea, false );
onScrollChange();
}
void UITerminal::onPositionChange() {
mTerm->setPosition( mScreenPos.trunc() );
UIWidget::onPositionChange();
@@ -0,0 +1,131 @@
#include "utest.h"
#include <eepp/ui/uiscrollcontroller.hpp>
using namespace EE;
using namespace EE::System;
using namespace EE::UI;
UTEST( UIScrollController, InstantScrollRetainsFractionalInputAndClamps ) {
UIScrollController controller;
const Vector2f maximum( 100.f, 200.f );
EXPECT_TRUE( controller.scrollBy( { 4.25f, -20.f }, { 10.5f, 5.5f }, maximum, false ) );
EXPECT_NEAR( 14.75f, controller.getPosition().x, 0.0001f );
EXPECT_NEAR( 0.f, controller.getPosition().y, 0.0001f );
EXPECT_FALSE( controller.isActive() );
EXPECT_FALSE( controller.scrollBy( { 200.f, -1.f }, { 100.f, 0.f }, maximum, false ) );
}
UTEST( UIScrollController, SmoothScrollAccumulatesTargetsWithoutJumping ) {
UIScrollController controller;
const Vector2f maximum( 500.f, 500.f );
EXPECT_TRUE( controller.scrollBy( { 0.f, 100.f }, Vector2f::Zero, maximum, true ) );
EXPECT_TRUE( controller.scrollBy( { 0.f, 25.5f }, Vector2f::Zero, maximum, true ) );
EXPECT_NEAR( 0.f, controller.getPosition().y, 0.0001f );
EXPECT_NEAR( 125.5f, controller.getTarget().y, 0.0001f );
EXPECT_TRUE( controller.isActive() );
controller.update( Milliseconds( 16 ), maximum );
EXPECT_GT( controller.getPosition().y, 0.f );
EXPECT_LT( controller.getPosition().y, controller.getTarget().y );
for ( size_t i = 0; i < 100; ++i )
controller.update( Milliseconds( 16 ), maximum );
EXPECT_NEAR( 125.5f, controller.getPosition().y, 0.0001f );
EXPECT_FALSE( controller.isActive() );
}
UTEST( UIScrollController, SmoothWheelStepsOverlapInsteadOfExtendingGestureDuration ) {
UIScrollController controller;
const Vector2f maximum( 500.f, 500.f );
controller.setWheelScrollDuration( Milliseconds( 100 ) );
controller.scrollBy( { 0.f, 10.f }, Vector2f::Zero, maximum, true );
controller.update( Milliseconds( 50 ), maximum );
EXPECT_NEAR( 7.5f, controller.getPosition().y, 0.0001f );
controller.scrollBy( { 0.f, 10.f }, controller.getPosition(), maximum, true );
controller.update( Milliseconds( 50 ), maximum );
EXPECT_NEAR( 17.5f, controller.getPosition().y, 0.0001f );
EXPECT_TRUE( controller.isActive() );
controller.update( Milliseconds( 50 ), maximum );
EXPECT_NEAR( 20.f, controller.getPosition().y, 0.0001f );
EXPECT_FALSE( controller.isActive() );
}
UTEST( UIScrollController, PartialWheelStepUsesProportionallyShorterTween ) {
UIScrollController controller;
const Vector2f maximum( 100.f, 100.f );
controller.setWheelScrollDuration( Milliseconds( 100 ) );
controller.scrollBy( { 0.f, 10.f }, { 0.f, 95.f }, maximum, true );
controller.update( Milliseconds( 25 ), maximum );
EXPECT_NEAR( 98.75f, controller.getPosition().y, 0.0001f );
EXPECT_TRUE( controller.isActive() );
controller.update( Milliseconds( 25 ), maximum );
EXPECT_NEAR( 100.f, controller.getPosition().y, 0.0001f );
EXPECT_FALSE( controller.isActive() );
}
UTEST( UIScrollController, SubTickInputScalesTweenDuration ) {
UIScrollController controller;
const Vector2f maximum( 100.f, 100.f );
controller.setWheelScrollDuration( Milliseconds( 100 ) );
controller.scrollBy( { 0.f, 2.5f }, Vector2f::Zero, maximum, true, 0.25f );
controller.update( Milliseconds( 12.5 ), maximum );
EXPECT_NEAR( 1.875f, controller.getPosition().y, 0.0001f );
EXPECT_TRUE( controller.isActive() );
controller.update( Milliseconds( 12.5 ), maximum );
EXPECT_NEAR( 2.5f, controller.getPosition().y, 0.0001f );
EXPECT_FALSE( controller.isActive() );
}
static UIScrollController smoothControllerAfter( size_t frames, const Time& elapsed ) {
UIScrollController controller;
controller.scrollBy( { 0.f, 300.f }, Vector2f::Zero, { 1000.f, 1000.f }, true );
for ( size_t i = 0; i < frames; ++i )
controller.update( elapsed, { 1000.f, 1000.f } );
return controller;
}
UTEST( UIScrollController, SmoothMotionIsFrameRateIndependent ) {
const auto atSixtyHz = smoothControllerAfter( 15, Seconds( 1. / 60. ) );
const auto atOneTwentyHz = smoothControllerAfter( 30, Seconds( 1. / 120. ) );
EXPECT_NEAR( atSixtyHz.getPosition().y, atOneTwentyHz.getPosition().y, 0.001f );
}
static UIScrollController momentumControllerAfter( size_t frames, const Time& elapsed ) {
UIScrollController controller;
const Vector2f maximum( 1000.f, 1000.f );
controller.beginDrag( { 500.f, 500.f }, maximum );
controller.dragBy( { 0.f, -20.f }, Milliseconds( 16 ), maximum );
controller.endDrag();
for ( size_t i = 0; i < frames; ++i )
controller.update( elapsed, maximum );
return controller;
}
UTEST( UIScrollController, DragReleaseProducesFrameRateIndependentMomentum ) {
const auto atSixtyHz = momentumControllerAfter( 30, Seconds( 1. / 60. ) );
const auto atOneTwentyHz = momentumControllerAfter( 60, Seconds( 1. / 120. ) );
EXPECT_LT( atSixtyHz.getPosition().y, 480.f );
EXPECT_NEAR( atSixtyHz.getPosition().y, atOneTwentyHz.getPosition().y, 0.001f );
EXPECT_NEAR( atSixtyHz.getVelocity().y, atOneTwentyHz.getVelocity().y, 0.001f );
}
UTEST( UIScrollController, MomentumStopsAtScrollBounds ) {
UIScrollController controller;
const Vector2f maximum( 100.f, 100.f );
controller.beginDrag( { 50.f, 5.f }, maximum );
controller.dragBy( { 0.f, -20.f }, Milliseconds( 16 ), maximum );
controller.endDrag();
controller.update( Milliseconds( 16 ), maximum );
EXPECT_NEAR( 0.f, controller.getPosition().y, 0.0001f );
EXPECT_NEAR( 0.f, controller.getVelocity().y, 0.0001f );
EXPECT_FALSE( controller.isActive() );
}
@@ -0,0 +1,88 @@
#include "utest.h"
#include <eepp/system/filesystem.hpp>
#include <eepp/ui/uiapplication.hpp>
#include <eepp/ui/uicodeeditor.hpp>
#include <eepp/ui/uiscenenode.hpp>
using namespace EE;
using namespace EE::System;
using namespace EE::UI;
class ScrollingTestEditor : public UICodeEditor {
public:
ScrollingTestEditor() : UICodeEditor() {}
using UICodeEditor::onMouseWheel;
};
UTEST( UIScrolling, SceneSmoothScrollingDefaultsCanBeInheritedSnapshottedAndAppliedNow ) {
UIApplication app(
WindowSettings{ 320, 240, "eepp - scrolling test" },
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
auto* scene = app.getUI();
auto* editor = UICodeEditor::New();
editor->setPixelsSize( 160, 80 );
editor->setParent( scene->getRoot() );
editor->getDocument().textInput( String( 100, '\n' ) );
scene->flushDirtyStyleAndLayout();
EXPECT_FALSE( editor->isSmoothScrollEnabled() );
scene->setSmoothScrollEnabled( true );
EXPECT_TRUE( scene->isSmoothScrollEnabled() );
EXPECT_FALSE( editor->isSmoothScrollEnabled() );
auto* newEditor = UICodeEditor::New();
newEditor->setParent( scene->getRoot() );
EXPECT_TRUE( newEditor->isSmoothScrollEnabled() );
newEditor->setSmoothScrollEnabled( false );
EXPECT_FALSE( newEditor->isSmoothScrollEnabled() );
auto* embeddedHost = UIWidget::New();
embeddedHost->setParent( scene->getRoot() );
auto* embeddedScene = UISceneNode::New();
embeddedScene->setParent( embeddedHost );
EXPECT_TRUE( embeddedScene->isSmoothScrollEnabled() );
UICodeEditor* embeddedEditor;
{
auto context = embeddedScene->makeCurrent();
embeddedEditor = UICodeEditor::New();
embeddedEditor->setParent( embeddedScene->getRoot() );
}
EXPECT_TRUE( embeddedEditor->isSmoothScrollEnabled() );
embeddedScene->setSmoothScrollEnabled( false, true );
EXPECT_FALSE( scene->isSmoothScrollEnabled() );
EXPECT_FALSE( embeddedScene->isSmoothScrollEnabled() );
EXPECT_FALSE( editor->isSmoothScrollEnabled() );
EXPECT_FALSE( newEditor->isSmoothScrollEnabled() );
EXPECT_FALSE( embeddedEditor->isSmoothScrollEnabled() );
editor->setSmoothScrollEnabled( true );
EXPECT_TRUE( editor->isSmoothScrollEnabled() );
ASSERT_GT( editor->getMaxScroll().y, 10.75f );
editor->setScroll( { 0.f, 10.75f } );
EXPECT_NEAR( 10.f, editor->getScroll().y, 0.0001f );
eeDelete( editor );
}
UTEST( UIScrolling, CodeEditorKeepsLegacyWheelStepButSoftensFractionalMomentumTail ) {
UIApplication app(
WindowSettings{ 320, 240, "eepp - wheel precision test" },
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
auto* editor = eeNew( ScrollingTestEditor, () );
editor->setPixelsSize( 160, 80 );
editor->setParent( app.getUI()->getRoot() );
editor->getDocument().textInput( String( 100, '\n' ) );
editor->setMouseWheelScroll( 50.f );
app.getUI()->flushDirtyStyleAndLayout();
editor->setScrollY( 100.f );
EXPECT_EQ( 1u, editor->onMouseWheel( { 0.f, -0.1f }, false ) );
EXPECT_NEAR( 105.f, editor->getScroll().y, 0.0001f );
EXPECT_EQ( 1u, editor->onMouseWheel( { 0.f, -4.f }, false ) );
EXPECT_NEAR( 155.f, editor->getScroll().y, 0.0001f );
eeDelete( editor );
}
+7 -1
View File
@@ -157,6 +157,10 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
ui.openProjectInNewWindow = ini.getValueB( "ui", "open_project_in_new_window", false );
ui.nativeFileDialogs = ini.getValueB( "ui", "native_file_dialogs", false );
ui.imagesQuickPreview = ini.getValueB( "ui", "images_quick_preview", false );
ui.smoothScroll = ini.getValueB(
"ui", "smooth_scroll",
ini.getValueB( "editor", "smooth_scroll",
ini.getValueB( "terminal", "smooth_scroll", false ) ) );
ui.panelPosition = panelPositionFromString( ini.getValue( "ui", "panel_position", "left" ) );
ui.sansSerifFont = ini.getValue( "ui", "serif_font", "fonts/NotoSans-Regular.ttf" );
ui.monospaceFont = ini.getValue( "ui", "monospace_font", "fonts/DejaVuSansMono.ttf" );
@@ -374,6 +378,9 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
ini.setValueB( "ui", "open_project_in_new_window", ui.openProjectInNewWindow );
ini.setValueB( "ui", "native_file_dialogs", ui.nativeFileDialogs );
ini.setValueB( "ui", "images_quick_preview", ui.imagesQuickPreview );
ini.setValueB( "ui", "smooth_scroll", ui.smoothScroll );
ini.deleteValue( "editor", "smooth_scroll" );
ini.deleteValue( "terminal", "smooth_scroll" );
ini.setValue( "ui", "panel_position", panelPositionToString( ui.panelPosition ) );
ini.setValue( "ui", "serif_font", ui.sansSerifFont );
ini.setValue( "ui", "monospace_font", ui.monospaceFont );
@@ -479,7 +486,6 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
term.scrollBarMode == ScrollBarMode::Auto
? "auto"sv
: ( term.scrollBarMode == ScrollBarMode::AlwaysOn ? "on"sv : "off" ) );
ini.setValueB( "window", "vsync", context.VSync );
ini.setValue( "window", "glversion",
Renderer::graphicsLibraryVersionToString( context.Version ) );
+1
View File
@@ -72,6 +72,7 @@ struct UIConfig {
bool nativeFileDialogs{ false };
bool imagesQuickPreview{ false };
bool editorFontInInputFields{ true };
bool smoothScroll{ false };
PanelPosition panelPosition{ PanelPosition::Left };
std::string sansSerifFont;
std::string monospaceFont;
+75 -68
View File
@@ -28,6 +28,7 @@
#include <eepp/ui/tools/uiaudioplayer.hpp>
#include <eepp/ui/tools/uidiffview.hpp>
#include <eepp/ui/tools/uiimageviewer.hpp>
#include <eepp/window/platformhelper.hpp>
#include <filesystem>
#include <iostream>
@@ -55,10 +56,6 @@ using namespace std::literals;
namespace fs = std::filesystem;
using json = nlohmann::json;
#if EE_PLATFORM == EE_PLATFORM_MACOS
#include "macos/macos.hpp"
#endif
namespace ecode {
Clock globalClock;
@@ -2159,77 +2156,85 @@ static Uint32 DefaultSwitchToStatusPanelModifier = KeyMod::getDefaultSecondaryMo
KeyBindings::ShortcutMap App::getLocalKeybindings() {
return {
{ { KEY_PRINTSCREEN, KEYMOD_NONE }, "take-screenshot" },
{ { KEY_RETURN, KeyMod::getDefaultSecondaryModifier() | KeyMod::getDefaultModifier() },
"fullscreen-toggle" },
{ { KEY_F3, KEYMOD_NONE }, "repeat-find" },
{ { KEY_F3, KEYMOD_SHIFT }, "find-prev" },
{ { KEY_F12, KEYMOD_NONE }, "console-toggle" },
{ { KEY_F, KeyMod::getDefaultModifier() }, "find-replace" },
{ { KEY_Q, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "close-app" },
{ { KEY_O, KeyMod::getDefaultModifier() }, "open-file" },
{ { KEY_W, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "download-file-web" },
{ { KEY_O, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-folder" },
{ { KEY_F11, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "debug-widget-tree-view" },
{ { KEY_K, KeyMod::getDefaultModifier() }, "open-locatebar" },
{ { KEY_P, KeyMod::getDefaultModifier() }, "open-command-palette" },
{ { KEY_COMMA, KeyMod::getDefaultModifier() }, "open-settings" },
{ { KEY_COMMA, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-project-settings" },
{ { KEY_F, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-global-search" },
{ { KEY_L, KeyMod::getDefaultModifier() }, "go-to-line" },
{ { KEY_RETURN, KeyMod::getDefaultSecondaryModifier() | KeyMod::getDefaultModifier() },
"fullscreen-toggle" },
{ { KEY_F3, KEYMOD_NONE }, "repeat-find" }, { { KEY_F3, KEYMOD_SHIFT }, "find-prev" },
{ { KEY_F12, KEYMOD_NONE }, "console-toggle" },
{ { KEY_F, KeyMod::getDefaultModifier() }, "find-replace" },
{ { KEY_Q, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "close-app" },
{ { KEY_O, KeyMod::getDefaultModifier() }, "open-file" },
{ { KEY_W, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "download-file-web" },
{ { KEY_O, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-folder" },
{ { KEY_F11, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "debug-widget-tree-view" },
{ { KEY_K, KeyMod::getDefaultModifier() }, "open-locatebar" },
{ { KEY_P, KeyMod::getDefaultModifier() }, "open-command-palette" },
{ { KEY_COMMA, KeyMod::getDefaultModifier() }, "open-settings" },
{ { KEY_COMMA, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-project-settings" },
{ { KEY_F, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-global-search" },
{ { KEY_L, KeyMod::getDefaultModifier() }, "go-to-line" },
#if EE_PLATFORM == EE_PLATFORM_MACOS
{ { KEY_M, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "menu-toggle" },
{ { KEY_M, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "menu-toggle" },
#else
{ { KEY_M, KeyMod::getDefaultModifier() }, "menu-toggle" },
{ { KEY_M, KeyMod::getDefaultModifier() }, "menu-toggle" },
#endif
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "save-all" },
{ { KEY_F9, KeyMod::getDefaultSecondaryModifier() }, "switch-side-panel" },
{ { KEY_J,
KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() | KEYMOD_SHIFT },
"terminal-split-left" },
{ { KEY_L,
KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() | KEYMOD_SHIFT },
"terminal-split-right" },
{ { KEY_I,
KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() | KEYMOD_SHIFT },
"terminal-split-top" },
{ { KEY_K,
KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() | KEYMOD_SHIFT },
"terminal-split-bottom" },
{ { KEY_S,
KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() | KEYMOD_SHIFT },
"terminal-split-swap" },
{ { KEY_T,
KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() | KEYMOD_SHIFT },
"reopen-closed-tab" },
{ { KEY_1, DefaultSwitchToStatusPanelModifier }, "toggle-status-locate-bar" },
{ { KEY_2, DefaultSwitchToStatusPanelModifier }, "toggle-status-global-search-bar" },
{ { KEY_3, DefaultSwitchToStatusPanelModifier }, "toggle-status-terminal" },
{ { KEY_4, DefaultSwitchToStatusPanelModifier }, "toggle-status-build-output" },
{ { KEY_5, DefaultSwitchToStatusPanelModifier }, "toggle-status-app-output" },
{ { KEY_B, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-start-cancel" },
{ { KEY_C, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-cancel" },
{ { KEY_R, KeyMod::getDefaultModifier() }, "project-build-and-run" },
{ { KEY_O, KeyMod::getDefaultSecondaryModifier() | KEYMOD_SHIFT }, "show-open-documents" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-workspace-symbol-search" },
{ { KEY_P, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-document-symbol-search" },
{ { KEY_N, KEYMOD_SHIFT | KeyMod::getDefaultSecondaryModifier() }, "create-new-window" },
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "save-all" },
{ { KEY_F9, KeyMod::getDefaultSecondaryModifier() }, "switch-side-panel" },
{ { KEY_J, KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() |
KEYMOD_SHIFT },
"terminal-split-left" },
{ { KEY_L, KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() |
KEYMOD_SHIFT },
"terminal-split-right" },
{ { KEY_I, KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() |
KEYMOD_SHIFT },
"terminal-split-top" },
{ { KEY_K, KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() |
KEYMOD_SHIFT },
"terminal-split-bottom" },
{ { KEY_S, KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() |
KEYMOD_SHIFT },
"terminal-split-swap" },
{ { KEY_T, KeyMod::getDefaultModifier() | KeyMod::getDefaultSecondaryModifier() |
KEYMOD_SHIFT },
"reopen-closed-tab" },
{ { KEY_1, DefaultSwitchToStatusPanelModifier }, "toggle-status-locate-bar" },
{ { KEY_2, DefaultSwitchToStatusPanelModifier }, "toggle-status-global-search-bar" },
{ { KEY_3, DefaultSwitchToStatusPanelModifier }, "toggle-status-terminal" },
{ { KEY_4, DefaultSwitchToStatusPanelModifier }, "toggle-status-build-output" },
{ { KEY_5, DefaultSwitchToStatusPanelModifier }, "toggle-status-app-output" },
{ { KEY_B, KeyMod::getDefaultModifier() | KEYMOD_SHIFT },
"project-build-start-cancel" },
{ { KEY_C, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-cancel" },
{ { KEY_R, KeyMod::getDefaultModifier() }, "project-build-and-run" },
{ { KEY_O, KeyMod::getDefaultSecondaryModifier() | KEYMOD_SHIFT },
"show-open-documents" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_SHIFT },
"open-workspace-symbol-search" },
{ { KEY_P, KeyMod::getDefaultModifier() | KEYMOD_SHIFT },
"open-document-symbol-search" },
{ { KEY_N, KEYMOD_SHIFT | KeyMod::getDefaultSecondaryModifier() },
"create-new-window" },
};
}
// Old keybindings will be rebinded to the new keybindings when they are still set to the old
// keybindind
std::map<std::string, std::string> App::getMigrateKeybindings() {
return { { "fullscreen-toggle", "alt+return" }, { "switch-to-tab-1", "alt+1" },
{ "switch-to-tab-2", "alt+2" }, { "switch-to-tab-3", "alt+3" },
{ "switch-to-tab-4", "alt+4" }, { "switch-to-tab-5", "alt+5" },
{ "switch-to-tab-6", "alt+6" }, { "switch-to-tab-7", "alt+7" },
{ "switch-to-tab-8", "alt+8" }, { "switch-to-tab-9", "alt+9" },
{ "switch-to-last-tab", "alt+0" },
return {
{ "fullscreen-toggle", "alt+return" }, { "switch-to-tab-1", "alt+1" },
{ "switch-to-tab-2", "alt+2" }, { "switch-to-tab-3", "alt+3" },
{ "switch-to-tab-4", "alt+4" }, { "switch-to-tab-5", "alt+5" },
{ "switch-to-tab-6", "alt+6" }, { "switch-to-tab-7", "alt+7" },
{ "switch-to-tab-8", "alt+8" }, { "switch-to-tab-9", "alt+9" },
{ "switch-to-last-tab", "alt+0" },
#if EE_PLATFORM == EE_PLATFORM_MACOS
{ "menu-toggle", "mod+shift+m" },
{ "menu-toggle", "mod+shift+m" },
#endif
{ "lock-toggle", "mod+shift+l" }, { "debug-widget-tree-view", "f11" },
{ "project-build-and-run", "f5" }, { "project-build-start", "mod+shift+b" } };
{ "lock-toggle", "mod+shift+l" }, { "debug-widget-tree-view", "f11" },
{ "project-build-and-run", "f5" }, {
"project-build-start", "mod+shift+b"
}
};
}
std::vector<std::string> App::getUnlockedCommands() {
@@ -4518,8 +4523,8 @@ void App::tintTitleBar() {
->getVariableByName( "--back" );
if ( !backVar.isEmpty() ) {
auto backColor( Color::fromString( backVar.getValue() ) );
macOS_changeTitleBarColor( mWindow->getWindowHandler(), backColor.r / 255.f,
backColor.g / 255.f, backColor.b / 255.f );
Engine::instance()->getPlatformHelper()->setWindowTitleBarColor(
mWindow->getWindowHandler(), backColor.r, backColor.g, backColor.b );
}
}
#endif
@@ -4748,8 +4753,9 @@ void App::init( InitParameters& params ) {
EE_PLATFORM == EE_PLATFORM_BSD
#if EE_PLATFORM == EE_PLATFORM_MACOS
macOS_enableScrollMomentum();
macOS_removeTitleBarSeparator( mWindow->getWindowHandler() );
engine->getPlatformHelper()->setNativeScrollMomentumEnabled( true );
engine->getPlatformHelper()->setWindowTitleBarSeparatorVisible( mWindow->getWindowHandler(),
false );
#endif
mThreadPool->run( [this]() {
@@ -4954,6 +4960,7 @@ void App::init( InitParameters& params ) {
mUISceneNode = UISceneNode::New();
mLifetime.setDispatcher( mUISceneNode );
mUISceneNode->setThreadPool( mThreadPool );
mUISceneNode->setSmoothScrollEnabled( mConfig.ui.smoothScroll );
mUIColorScheme = mConfig.ui.colorScheme;
if ( params.language.empty() )
-18
View File
@@ -1,18 +0,0 @@
#ifndef ECODE_MACOS_MACOS
#define ECODE_MACOS_MACOS
#ifdef __cplusplus
extern "C" {
#endif
void macOS_enableScrollMomentum();
void macOS_removeTitleBarSeparator( void* nsWindow );
void macOS_changeTitleBarColor( void* window, double red, double green, double blue );
#ifdef __cplusplus
}
#endif
#endif
-19
View File
@@ -1,19 +0,0 @@
#import <AppKit/AppKit.h>
#include <Cocoa/Cocoa.h>
#include "macos.hpp"
void macOS_enableScrollMomentum() {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"AppleMomentumScrollSupported"];
}
void macOS_removeTitleBarSeparator( void* window ) {
NSWindow* nsWindow = window;
[nsWindow setTitlebarSeparatorStyle:NSTitlebarSeparatorStyleNone];
}
void macOS_changeTitleBarColor( void* window, double red, double green, double blue ) {
NSWindow* nsWindow = window;
nsWindow.titlebarAppearsTransparent = YES;
nsWindow.backgroundColor = [NSColor colorWithRed:red green:green blue:blue alpha:1.];
}
+11 -3
View File
@@ -77,8 +77,9 @@ void SettingsPanel::create( Scope scope ) {
addProjectSettings( state );
}
state.panel->build();
state.connections += state.window->connect(
Event::OnWindowReady, [&state]( const Event* ) { state.panel->focusSearch(); } );
state.connections += state.window->connect( Event::OnWindowReady, [&state]( const Event* ) {
state.panel->runOnMainThread( [&state] { state.panel->focusSearch(); } );
} );
state.connections +=
state.window->connect( Event::OnWindowClose, [this, &state, scope]( const Event* ) {
for ( const auto& document : state.documents ) {
@@ -203,6 +204,13 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
mApp->i18n( "quick_preview_images_tooltip",
"Preview images without opening a permanent editor tab." ) },
&mApp->getConfig().ui.imagesQuickPreview );
addBool(
panel,
{ "smoothScroll", "general.behavior", mApp->i18n( "smooth_scroll", "Smooth Scrolling" ),
mApp->i18n( "smooth_scroll_desc",
"Animate scrolling from mouse wheels and trackpads." ) },
&mApp->getConfig().ui.smoothScroll,
[this]( bool value ) { mApp->getUISceneNode()->setSmoothScrollEnabled( value, true ); } );
addCategory( panel, "editor.appearance", mApp->i18n( "editor", "Editor" ),
mApp->i18n( "appearance", "Appearance" ) );
@@ -1157,7 +1165,7 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
[this, monitorRefreshRate, unlimitedFrameRate] {
const auto value = mApp->getConfig().context.FrameRateLimit;
return value == ContextSettings::FrameRateLimitScreenRefreshRate ? monitorRefreshRate
: value == 0 ? unlimitedFrameRate
: value == 0 ? unlimitedFrameRate
: String( String::toString( value ) );
},
[this, monitorRefreshRate, unlimitedFrameRate]( const String& selection ) {
+4
View File
@@ -33,6 +33,8 @@ void AppConfig::load() {
terminal.closeOnExit = mIni.getValueB( "terminal", "close_on_exit", terminal.closeOnExit );
terminal.initialTabs = mIni.getValueU( "terminal", "initial_tabs", terminal.initialTabs );
terminal.exclusiveMode = mIni.getValueB( "terminal", "exclusive_mode", terminal.exclusiveMode );
ui.smoothScroll = mIni.getValueB(
"ui", "smooth_scroll", mIni.getValueB( "terminal", "smooth_scroll", ui.smoothScroll ) );
const auto newTerminalBehavior =
mIni.getValue( "terminal", "new_terminal_behavior", "current" );
terminal.newTerminalBehavior =
@@ -104,6 +106,8 @@ bool AppConfig::savePreferences() {
mIni.setValueB( "terminal", "close_on_exit", terminal.closeOnExit );
mIni.setValueU( "terminal", "initial_tabs", terminal.initialTabs );
mIni.setValueB( "terminal", "exclusive_mode", terminal.exclusiveMode );
mIni.setValueB( "ui", "smooth_scroll", ui.smoothScroll );
mIni.deleteValue( "terminal", "smooth_scroll" );
mIni.setValue( "terminal", "new_terminal_behavior",
terminal.newTerminalBehavior == NewTerminalBehavior::VerticalSplit ? "vertical"
: terminal.newTerminalBehavior == NewTerminalBehavior::HorizontalSplit
+5
View File
@@ -38,6 +38,10 @@ struct TerminalConfig {
ScrollBarMode scrollBarMode{ ScrollBarMode::Auto };
};
struct UIConfig {
bool smoothScroll{ false };
};
struct FontConfig {
std::string path;
std::string fallbackPath;
@@ -76,6 +80,7 @@ struct AppConfig {
FontConfig font;
WindowConfig window;
ThemeConfig theme;
UIConfig ui;
WindowStateConfig windowState;
explicit AppConfig( std::string configPath );
+1
View File
@@ -724,6 +724,7 @@ int App::run( int argc, char* argv[] ) {
scene = app.getUI();
if ( !appWindow || !appWindow->isOpen() || !scene )
return EXIT_FAILURE;
scene->setSmoothScrollEnabled( config->ui.smoothScroll );
settingsActions = std::make_unique<SettingsActions>( this );
keybindingsPath = config->getConfigPath() + "keybindings.cfg";
loadKeybindings();
+8
View File
@@ -234,6 +234,14 @@ UIWindow* SettingsPanel::create( App& app ) {
[value]( UITerminal* terminal ) { terminal->setExclusiveMode( value ); } );
app.savePreferences();
} );
panel->addBool( { "smoothScroll", "terminal.behavior",
app.i18n( "smooth_scroll", "Smooth Scrolling" ),
app.i18n( "smooth_scroll_desc",
"Animate scrolling from mouse wheels and trackpads." ) },
&app.config->ui.smoothScroll, [&app]( bool value ) {
app.scene->setSmoothScrollEnabled( value, true );
app.savePreferences();
} );
panel->addBool(
{ "closeTerminalTabOnExit", "terminal.behavior",
app.i18n( "close_terminal_tab_on_exit", "Close Terminal Tab on Exit" ),