Commits (1)
  • Jeffrey Lee's avatar
    LineScroll scaling & smoother scrolling · a379c4e8
    Jeffrey Lee authored
    When performing a scroll operation, the previous version of the module
    would send all the scroll requests at once, causing the target to
    instantly jump to the new position. This new version will split the
    operation multiple chunks (with calls to Wimp_Poll / Wimp_PollIdle
    inbetween), which allows for:
    
    1. The scroll speed/scale factor to be applied when normal "scroll" wimp
    messages are used (i.e. "line scroll" option is enabled). This was
    effectively impossible to support in the previous version, because it
    requires the module to send a scroll request to the target task, wait
    for the target to have acted on it (i.e. wait for the next null poll),
    and then read the window state to determine how far it scrolled (so that
    future scroll requests can use that value to judge how many scroll
    requests must be sent to get the desired motion)
    
    2. Smooth (ish) scrolling for all scroll methods. Currently the code
    will attempt to scroll by one half of the remaining distance, per
    centisecond. This is enough for the motion to be visible to the user,
    while still fast enough to keep the desktop responsive.
    
    Version 0.02. Tagged as 'WindowScroll-0_02'
    a379c4e8
/* (0.01) /* (0.02)
* *
* This file is automatically maintained by srccommit, do not edit manually. * This file is automatically maintained by srccommit, do not edit manually.
* *
*/ */
#define Module_MajorVersion_CMHG 0.01 #define Module_MajorVersion_CMHG 0.02
#define Module_MinorVersion_CMHG #define Module_MinorVersion_CMHG
#define Module_Date_CMHG 10 Mar 2020 #define Module_Date_CMHG 29 Apr 2020
#define Module_MajorVersion "0.01" #define Module_MajorVersion "0.02"
#define Module_Version 1 #define Module_Version 2
#define Module_MinorVersion "" #define Module_MinorVersion ""
#define Module_Date "10 Mar 2020" #define Module_Date "29 Apr 2020"
#define Module_ApplicationDate "10-Mar-20" #define Module_ApplicationDate "29-Apr-20"
#define Module_ComponentName "WindowScroll" #define Module_ComponentName "WindowScroll"
#define Module_FullVersion "0.01" #define Module_FullVersion "0.02"
#define Module_HelpVersion "0.01 (10 Mar 2020)" #define Module_HelpVersion "0.02 (29 Apr 2020)"
#define Module_LibraryVersionInfo "0:1" #define Module_LibraryVersionInfo "0:2"
...@@ -107,6 +107,7 @@ _kernel_oserror* module_init (const char *cmd_tail, int podule_base, void *pw) ...@@ -107,6 +107,7 @@ _kernel_oserror* module_init (const char *cmd_tail, int podule_base, void *pw)
debug_initialise(Module_Title, "", ""); debug_initialise(Module_Title, "", "");
debug_set_device(REPORTER_OUTPUT); debug_set_device(REPORTER_OUTPUT);
debug_set_unbuffered_files(TRUE); debug_set_unbuffered_files(TRUE);
debug_set_stamp_debug(TRUE);
/* Check that we can actually read the scroll wheel position */ /* Check that we can actually read the scroll wheel position */
e = _swix(OS_Pointer,_IN(0)|_OUTR(0,1),OSPointer_ReadAltPosition,&last_x,&last_y); e = _swix(OS_Pointer,_IN(0)|_OUTR(0,1),OSPointer_ReadAltPosition,&last_x,&last_y);
......
...@@ -50,10 +50,26 @@ typedef struct { ...@@ -50,10 +50,26 @@ typedef struct {
wimp_i i; wimp_i i;
} wimp_msgscroll; } wimp_msgscroll;
typedef struct {
int ticks_to_apply; /* Number of wheel ticks to apply */
int osunits_to_apply; /* Number of OS units to apply */
int last_ticks; /* Number of wheel ticks applied in the last update */
int scroll_offset; /* Scroll position before last_ticks was applied */
int osunits_per_tick; /* How many OS units we think one tick is (calculated by watching how far the target window scrolls) */
} scroll_vals;
wimp_t taskhandle = TASKHANDLE_NONE; wimp_t taskhandle = TASKHANDLE_NONE;
int pollword; int pollword;
bool abort_scroll; bool abort_scroll;
int last_x,last_y; int last_x,last_y; /* Last known wheel positions */
/* The thing we're scrolling */
static scroll_target target = {
.w = NO_WINDOW,
.i = NO_ICON,
};
static bool target_is_menu;
static scroll_vals scroll_x,scroll_y;
static const wimp_msgaction all_messages[] = { static const wimp_msgaction all_messages[] = {
wimp_MCLOSEDOWN, wimp_MCLOSEDOWN,
...@@ -81,6 +97,14 @@ static int diff(int old,int new) ...@@ -81,6 +97,14 @@ static int diff(int old,int new)
return new_delta; return new_delta;
} }
static void stop_scroll(void)
{
memset(&scroll_x,0,sizeof(scroll_x));
memset(&scroll_y,0,sizeof(scroll_y));
target.w = NO_WINDOW;
abort_scroll = false;
}
static scroll_target select_scroll_target(void) static scroll_target select_scroll_target(void)
{ {
scroll_target ct = { scroll_target ct = {
...@@ -143,7 +167,150 @@ static scroll_target select_scroll_target(void) ...@@ -143,7 +167,150 @@ static scroll_target select_scroll_target(void)
return ct; return ct;
} }
static void scroll_update(void) #ifdef DEBUGLIB
static const char *scroll_modes[] = {"Extended scroll", "Normal scroll", "OpenWindow"};
#endif
static inline bool measure_distance(void)
{
/* For "normal" scrolling (i.e. wimp_ESCROLL messages), we don't really
know how far the target window is going to scroll in response to
our request. This makes the scroll speed setting a bit useless.
To counter this, each time we send a scroll request, we'll wait
until the task has been able to react to it (wait for next null poll)
and then check to see how far the window has scrolled. We can then
use that value to gauge how many extra scroll requests we should
send in order to apply the speed setting.
Extended scroll requests face a similar problem, but because they
could be used for literally anything (e.g. zoom in/out) we currently
don't attempt to apply any scaling for them.
*/
return target.mode == SCROLL_NORMAL;
}
static int scroll_vals_update(scroll_vals *vals,int delta,bool enable,int new_scroll_offset,int delta_time)
{
/* Only scroll in this direction if the window allows it */
if (!enable)
{
memset(vals,0,sizeof(scroll_vals));
return 0;
}
bool measure = measure_distance();
if (vals->last_ticks && measure)
{
int win_delta = new_scroll_offset - vals->scroll_offset;
/* Update osunits_per_tick */
vals->osunits_per_tick = win_delta / vals->last_ticks;
/* If the window scrolled in the expected manner, reduce the
number of OS units we need to scroll by */
bool valid_delta = false;
if (vals->osunits_per_tick > 0)
{
if (((win_delta > 0) && (vals->osunits_to_apply >= win_delta))
|| ((win_delta < 0) && (vals->osunits_to_apply <= win_delta)))
{
vals->osunits_to_apply -= win_delta;
valid_delta = true;
}
}
else
{
/* Window scrolled in the wrong direction (or not at all) */
vals->osunits_per_tick = 0;
}
if (!valid_delta)
{
/* The window scrolled in an unexpected manner, or
scrolled too far - reset osunits_to_apply */
vals->osunits_to_apply = 0;
}
}
/* Update values
Note that settings.speed is in units of 4 OS units, to ensure that
we don't attempt to perform sub-pixel scrolling
*/
vals->ticks_to_apply += delta;
vals->osunits_to_apply += delta*(settings.speed<<2);
vals->scroll_offset = new_scroll_offset;
vals->last_ticks = 0;
if ((target.mode != SCROLL_MANUAL) && (vals->ticks_to_apply != 0) && (SIGN(vals->ticks_to_apply) != SIGN(vals->osunits_to_apply)))
{
/* Somehow our OS unit counter has gone out of sync with the
scroll wheel ticks - reset it so that the wheel ticks take
priority */
vals->osunits_to_apply = 0;
}
if (!delta_time)
{
return 0;
}
int return_val = 0;
do
{
/* Calculate return value (how far to scroll this update)
For SCROLL_MANUAL, this is in OS units
For other types, this is a tick count
*/
if (target.mode == SCROLL_MANUAL)
{
/* Smooth(ish) scrolling: Scroll by half the desired amount */
delta = (vals->osunits_to_apply/2) & ~3;
if (!delta)
{
delta = vals->osunits_to_apply;
}
vals->osunits_to_apply -= delta;
}
else
{
delta = vals->ticks_to_apply / 2;
if (!delta)
{
delta = vals->ticks_to_apply;
}
/* If we have an osunits_to_ticks value, use that to
calculate how many ticks to apply */
if (vals->osunits_per_tick)
{
int delta2 = (vals->osunits_to_apply / vals->osunits_per_tick) - return_val;
/* Again, aim to scroll by half */
if ((delta2 > 1) || (delta2 < -1))
{
delta2 /= 2;
}
/* Scroll by the max of the two values */
if (((delta2 > delta) && (delta >= 0))
|| ((delta2 < delta) && (delta <= 0)))
{
delta = delta2;
}
}
vals->ticks_to_apply -= delta;
/* Don't allow OS unit scrolling to cause the tick
counter to overshoot */
if (vals->ticks_to_apply && (SIGN(vals->ticks_to_apply) != SIGN(delta)))
{
vals->ticks_to_apply = 0;
}
}
return_val += delta;
} while (--delta_time && delta);
if (measure)
{
vals->last_ticks = return_val;
}
return return_val;
}
static void scroll_update(int delta_time)
{ {
int x,y; int x,y;
wimp_msgscroll msg; wimp_msgscroll msg;
...@@ -153,66 +320,104 @@ static void scroll_update(void) ...@@ -153,66 +320,104 @@ static void scroll_update(void)
dprintf(("","New scroll pos %d,%d\n",x,y)); dprintf(("","New scroll pos %d,%d\n",x,y));
} }
int dx = 0, dy = 0; int dx = diff(last_x,x);
if (!abort_scroll) int dy = diff(last_y,y);
{
dx = diff(last_x,x);
dy = diff(last_y,y);
dprintf(("","New scroll delta %d,%d\n",dx,dy));
}
last_x = x; last_x = x;
last_y = y; last_y = y;
abort_scroll = false;
if ((dx | dy) == 0) if (abort_scroll)
{ {
stop_scroll();
return; return;
} }
scroll_target target = select_scroll_target(); scroll_target t2;
bool target_is_menu = false; if ((target.w == NO_WINDOW) || dx || dy)
dprintf(("","Scroll target %x %x, mode %d\n",target.w,target.i,target.mode)); {
if ((target.w != NO_WINDOW) && (target.mode == SCROLL_MANUAL)) /* Start of a scroll operation, or we've received new wheel
input - find a target */
t2 = select_scroll_target();
}
else
{ {
/* Menus are special, the Wimp will ignore any /* Continuing a scroll - check the target hasn't changed state */
OpenWindow requests we try sending. However t2 = examine_window(target.w,target.i,NULL);
we can use Wimp_OpenWindow instead. But
using Wimp_OpenWindow for other windows is
probably a bit rude, so first we need to
work out whether this is a menu or not.
Send a null message to the window, and check
if the returned task handle is the magic
menu owner task handle. */
int handle=0;
_swix(Wimp_SendMessage,_INR(0,3)|_OUT(2),wimp_ENULL, (wimp_msgstr*) &msg /* Non-null to avoid any traps */, target.w, target.i, &handle);
target_is_menu = (handle == 0x706d6957); /* "Wimp" */
} }
if (target.w == NO_WINDOW) if (t2.w == NO_WINDOW)
{ {
dprintf(("","Stop: No target\n")); /* No target (or old target is now invalid) -> stop */
stop_scroll();
return; return;
} }
if (target.w == NO_WINDOW)
{
/* Start of a scroll operation. Use t2 as the target
(guaranteed to be good at this point) */
target = t2;
target_is_menu = false;
if (target.mode == SCROLL_MANUAL)
{
/* Menus are special, the Wimp will ignore any
OpenWindow requests we try sending. However
we can use Wimp_OpenWindow instead. But
using Wimp_OpenWindow for other windows is
probably a bit rude, so first we need to
work out whether this is a menu or not.
Send a null message to the window, and check
if the returned task handle is the magic
menu owner task handle. */
int handle=0;
_swix(Wimp_SendMessage,_INR(0,3)|_OUT(2),wimp_ENULL, (wimp_msgstr*) &msg /* Non-null to avoid any traps */, target.w, target.i, &handle);
target_is_menu = (handle == 0x706d6957); /* "Wimp" */
}
}
else
{
/* Continuing a scroll - check the target is the same as before */
if (memcmp(&target,&t2,sizeof(scroll_target)))
{
dprintf(("","Stop: Target changed state\n"));
stop_scroll();
/* Ignore the new delta (if any) - if things have
become invalid because the mouse moved, then we
don't really know whether the wheel change occurred
at the old location or the new one
*/
return;
}
}
dprintf(("","Scroll target %x %x\n",target.w,target.i));
if (wimp_get_wind_state(target.w,(wimp_wstate*) &msg)) if (wimp_get_wind_state(target.w,(wimp_wstate*) &msg))
{ {
dprintf(("","Stop: Couldn't get state\n")); dprintf(("","Stop: Couldn't get state\n"));
stop_scroll();
return; return;
} }
if (!target.x) dx = scroll_vals_update(&scroll_x,dx,target.x,msg.w.x,delta_time);
{ dy = scroll_vals_update(&scroll_y,dy,target.y,msg.w.y,delta_time);
dx = 0;
} if (!delta_time)
if (!target.y)
{ {
dy = 0; /* If we don't want to scroll yet (we're just measuring how far
the target scrolled in response to our request), exit */
return;
} }
dprintf(("","New scroll delta %d,%d ticks (%d,%d units) %dcs\n", scroll_x.ticks_to_apply, scroll_y.ticks_to_apply, scroll_x.osunits_to_apply, scroll_y.osunits_to_apply, delta_time));
if (!dx && !dy) if (!dx && !dy)
{ {
/* Finished scrolling */
stop_scroll();
return; return;
} }
dprintf(("","Scroll by %d,%d\n",dx,dy)); dprintf(("","Scroll by %d,%d (%s)\n",dx,dy,scroll_modes[target.mode]));
switch(target.mode) switch(target.mode)
{ {
...@@ -234,21 +439,8 @@ static void scroll_update(void) ...@@ -234,21 +439,8 @@ static void scroll_update(void)
break; break;
case SCROLL_MANUAL: case SCROLL_MANUAL:
/* TODO need to clamp within work area bounds? */ /* TODO need to clamp within work area bounds? */
/* Apply speed multiplier for manual scrolling msg.w.x += dx;
For the other modes, speed multipliers are dangerous, since msg.w.y += dy;
we don't really know how much they consider to be one scroll
tick (e.g. NetSurf scrolls by half the window size)
*/
dx *= settings.speed;
dy *= settings.speed;
/* We want scroll speeds to remain consistent regardless of
screen DPI. We also want to avoid scrolling by sub-pixel
amounts, because that generally doesn't work. So apply a
scale factor here to ensure pixel multiples are used
(assuming a maximum eigen value of 2!). This means the
user-configurable speed value is in units of 4 OS untis. */
msg.w.x += dx<<2;
msg.w.y += dy<<2;
if (target_is_menu) if (target_is_menu)
{ {
wimp_open_wind(&msg.w); wimp_open_wind(&msg.w);
...@@ -279,20 +471,63 @@ int main(int argc,char **argv) ...@@ -279,20 +471,63 @@ int main(int argc,char **argv)
quit(e); quit(e);
/* Poll */ /* Poll */
int last_idle_time = 0,delta_time;
do { do {
wimp_eventstr event; wimp_eventstr event;
pollword = 0; pollword = 0;
e = (os_error *) _swix(Wimp_Poll,_INR(0,1)|_IN(3)|_OUT(0), bool scrolling = (target.w != NO_WINDOW);
wimp_EMNULL|wimp_EMREDRAW|wimp_EMPTRLEAVE|wimp_EMPTRENTER|wimp_EMBUT|wimp_EMKEY|wimp_EMLOSECARET|wimp_EMGAINCARET|wimp_EMACK|(1<<22)|(1<<24), bool measuring = scrolling && measure_distance() && (scroll_x.last_ticks || scroll_y.last_ticks);
/* scrolling = false:
We're idle, so just wait for pollword non-zero.
scrolling = true, measuring = false:
We're scrolling, use PollIdle to rate limit ourselves.
scrolling = true, measuring = true:
We're scrolling, and we've just sent a scroll request
where we want to measure how far the target window
scrolls. Come back as soon as possible.
*/
bool pollidle = scrolling && !measuring;
if (!scrolling)
{
/* Not scrolling, can forget old timestamp */
last_idle_time = 0;
}
else if (!last_idle_time && pollidle)
{
/* We care about the time, and we don't have it yet */
last_idle_time = _swi(OS_ReadMonotonicTime,_RETURN(0));
}
e = (os_error *) _swix(pollidle ? Wimp_PollIdle : Wimp_Poll,_INR(0,3)|_OUT(0),
(scrolling ? 0 : wimp_EMNULL)|wimp_EMREDRAW|wimp_EMPTRLEAVE|wimp_EMPTRENTER|wimp_EMBUT|wimp_EMKEY|wimp_EMLOSECARET|wimp_EMGAINCARET|wimp_EMACK|(1<<22)|(1<<24),
&event.data, &event.data,
last_idle_time + 1,
&pollword, &pollword,
&event.e); &event.e);
if(e) if(e)
quit(e); quit(e);
switch(event.e) switch(event.e)
{ {
case wimp_ENULL:
case 13: /* Pollword non-zero */ case 13: /* Pollword non-zero */
scroll_update(); delta_time = last_idle_time;
last_idle_time = _swi(OS_ReadMonotonicTime,_RETURN(0));
if (!scrolling)
{
/* Force to 1 tick if this is the start of a scroll operation */
delta_time = 1;
}
else if (pollidle && (event.e == wimp_ENULL))
{
/* Wimp_PollIdle has called us back, calculate the real delta time */
delta_time = last_idle_time - delta_time;
}
else
{
/* Null event when measuring, or pollword non-zero
Update our state but don't perform any new scroll action */
delta_time = 0;
}
scroll_update(delta_time);
break; break;
case wimp_ESEND: case wimp_ESEND:
case wimp_ESENDWANTACK: case wimp_ESENDWANTACK:
......
...@@ -37,6 +37,8 @@ ...@@ -37,6 +37,8 @@
#include "DebugLib/DebugLib.h" #include "DebugLib/DebugLib.h"
//#define WINDOW_DEBUG
/* malloc/free functions which maintain a list of blocks to free */ /* malloc/free functions which maintain a list of blocks to free */
static void *chunk_alloc(size_t len,uintptr_t **list) static void *chunk_alloc(size_t len,uintptr_t **list)
{ {
...@@ -240,7 +242,9 @@ scroll_target examine_window(wimp_w w,wimp_i i,bool *is_pane) ...@@ -240,7 +242,9 @@ scroll_target examine_window(wimp_w w,wimp_i i,bool *is_pane)
} }
if (!ret.x && !ret.y) if (!ret.x && !ret.y)
{ {
#ifdef WINDOW_DEBUG
dprintf(("","flags %08x\n",wind.info.flags)); dprintf(("","flags %08x\n",wind.info.flags));
#endif
/* No scrolling */ /* No scrolling */
if (is_pane) if (is_pane)
{ {
...@@ -288,7 +292,9 @@ scroll_target find_scrollable_window(wimp_w w,wimp_i i) ...@@ -288,7 +292,9 @@ scroll_target find_scrollable_window(wimp_w w,wimp_i i)
/* Starting from the given window & icon, find a scrollable window */ /* Starting from the given window & icon, find a scrollable window */
scroll_target ret; scroll_target ret;
bool pane; bool pane;
#ifdef WINDOW_DEBUG
dprintf(("","start %08x\n",w)); dprintf(("","start %08x\n",w));
#endif
while (true) while (true)
{ {
ret = examine_window(w,i,&pane); ret = examine_window(w,i,&pane);
...@@ -303,7 +309,9 @@ scroll_target find_scrollable_window(wimp_w w,wimp_i i) ...@@ -303,7 +309,9 @@ scroll_target find_scrollable_window(wimp_w w,wimp_i i)
{ {
return ret; return ret;
} }
#ifdef WINDOW_DEBUG
dprintf(("","parent %08x\n",w)); dprintf(("","parent %08x\n",w));
#endif
} }
} }
......