GitHub #326: browser scrolling should be much improved

Code now convinces browser to rebuild when the tree changes by UI.
When widgets are move, the current widget should always be visible.
It's the responsibility of the UI callback to update the browser.
This commit is contained in:
Matthias Melcher 2021-12-17 18:34:51 +01:00 committed by Matthias Melcher
parent ba3041be6c
commit 3626e82057
8 changed files with 121 additions and 32 deletions

View File

@ -23,6 +23,7 @@
#include "fluid.h"
#include "file.h"
#include "code.h"
#include "widget_browser.h"
#include <FL/Fl.H>
#include <FL/Fl_Group.H>
@ -90,6 +91,7 @@ void group_cb(Fl_Widget *, void *) {
t = nxt;
}
fix_group_size(n);
widget_browser->rebuild();
}
void ungroup_cb(Fl_Widget *, void *) {
@ -114,6 +116,7 @@ void ungroup_cb(Fl_Widget *, void *) {
n = nxt;
}
delete q;
widget_browser->rebuild();
}
////////////////////////////////////////////////////////////////

View File

@ -112,7 +112,9 @@ void select_none_cb(Fl_Widget *,void *) {
selection_changed(p);
}
// move selected widgets in their parent's list:
/**
Callback to move all selected items before their previous unselected sibling.
*/
void earlier_cb(Fl_Widget*,void*) {
Fl_Type *f;
int mod = 0;
@ -129,8 +131,13 @@ void earlier_cb(Fl_Widget*,void*) {
f = nxt;
}
if (mod) set_modflag(1);
widget_browser->display(Fl_Type::current);
widget_browser->rebuild();
}
/**
Callback to move all selected items after their next unselected sibling.
*/
void later_cb(Fl_Widget*,void*) {
Fl_Type *f;
int mod = 0;
@ -147,6 +154,8 @@ void later_cb(Fl_Widget*,void*) {
f = prv;
}
if (mod) set_modflag(1);
widget_browser->display(Fl_Type::current);
widget_browser->rebuild();
}
static void delete_children(Fl_Type *p) {
@ -154,7 +163,6 @@ static void delete_children(Fl_Type *p) {
for (f = p; f && f->next && f->next->level > p->level; f = f->next) {/*empty*/}
for (; f != p; ) {
Fl_Type *g = f->prev;
widget_browser->deleting(f);
delete f;
f = g;
}
@ -166,7 +174,6 @@ void delete_all(int selected_only) {
if (f->selected || !selected_only) {
delete_children(f);
Fl_Type *g = f->next;
widget_browser->deleting(f);
delete f;
f = g;
} else f = f->next;
@ -275,7 +282,6 @@ Fl_Type::Fl_Type() {
*/
Fl_Type::~Fl_Type() {
// warning: destructor only works for widgets that have been add()ed.
if (widget_browser) widget_browser->deleting(this);
if (prev) prev->next = next; else first = next;
if (next) next->prev = prev; else last = prev;
if (Fl_Type::last==this) Fl_Type::last = prev;
@ -394,8 +400,6 @@ void Fl_Type::add(Fl_Type *p, Strategy strategy) {
// run the p tree a last time to make sure the widget_browser updates correctly
Fl_Type *a = p;
for (Fl_Type *t = this; t && a != end; a = t, t = t->next)
widget_browser->inserting(a, t);
widget_browser->redraw();
}
@ -430,9 +434,6 @@ void Fl_Type::insert(Fl_Type *g) {
if (parent) parent->add_child(this, g);
// run this tree a last time to make sure the widget_browser updates correctly
Fl_Type *a = prev;
for (Fl_Type *t = this; t && a != end; a = t, t = t->next)
if (a)
widget_browser->inserting(a, t);
widget_browser->redraw();
}
@ -485,8 +486,6 @@ Fl_Type *Fl_Type::remove() {
if (parent) parent->remove_child(this);
parent = 0;
// tell the widget_browser that we removed some nodes
for (Fl_Type *t = this; t; t = t->next)
widget_browser->deleting(t);
widget_browser->redraw();
selection_changed(0);
return r;
@ -532,18 +531,17 @@ void Fl_Type::open() {
/**
Move this node (and its children) into list before g.
Both `this` and `g` must be in the widget browser.
The caller must make sure that the widget browser is rebuilt correctly.
\param[in] g move \c this tree before \c g
*/
void Fl_Type::move_before(Fl_Type* g) {
if (level != g->level) printf("move_before levels don't match! %d %d\n",
level, g->level);
// Find the last child in the list
Fl_Type* n;
Fl_Type *n;
for (n = next; n && n->level > level; n = n->next) ;
if (n == g) return;
// Tell the widget browser that we delete them
for (n = next; n && n->level > level; n = n->next)
widget_browser->deleting(n);
// now link this tree before g
Fl_Type *l = n ? n->prev : Fl_Type::last;
prev->next = n;
@ -554,13 +552,6 @@ void Fl_Type::move_before(Fl_Type* g) {
g->prev = l;
// tell parent that it has a new child, so it can update itself
if (parent && is_widget()) parent->move_child(this,g);
// run this tree a last time to make sure the widget_browser updates correctly
Fl_Type *a = prev;
for (Fl_Type *t = this; t && a != n; a = t, t = t->next)
if (a)
widget_browser->inserting(a, t);
widget_browser->display(this);
widget_browser->redraw();
}

View File

@ -1239,6 +1239,8 @@ int Fl_Window_Type::handle(int event) {
popupx = 0x7FFFFFFF;
popupy = 0x7FFFFFFF; // mark as invalid (MAXINT)
in_this_only = NULL;
widget_browser->display(Fl_Type::current);
widget_browser->rebuild();
return 1;
}
case FL_PUSH:

View File

@ -37,6 +37,7 @@ copied or otherwise examined.
#include "Fl_Window_Type.h"
#include "factory.h"
#include "widget_panel.h"
#include "widget_browser.h"
#include <FL/platform.H>
#include <FL/Fl_Button.H>
@ -185,6 +186,8 @@ int Widget_Bin_Window_Button::handle(int inEvent)
w->position(Fl::event_x_root(), Fl::event_y_root());
}
}
widget_browser->display(Fl_Type::current);
widget_browser->rebuild();
}
return Fl_Button::handle(inEvent);
}

View File

@ -517,9 +517,11 @@ void revert_cb(Fl_Widget *,void *) {
undo_suspend();
if (!read_file(filename, 0)) {
undo_resume();
widget_browser->rebuild();
fl_message("Can't read %s: %s", filename, strerror(errno));
return;
}
widget_browser->rebuild();
undo_resume();
set_modflag(0, 0);
undo_clear();
@ -652,6 +654,7 @@ void open_cb(Fl_Widget *, void *v) {
undo_suspend();
if (!read_file(c, v!=0)) {
undo_resume();
widget_browser->rebuild();
fl_message("Can't read %s: %s", c, strerror(errno));
free((void *)filename);
filename = oldfilename;
@ -659,6 +662,7 @@ void open_cb(Fl_Widget *, void *v) {
return;
}
undo_resume();
widget_browser->rebuild();
if (v) {
// Inserting a file; restore the original filename...
free((void *)filename);
@ -697,6 +701,7 @@ void open_history_cb(Fl_Widget *, void *v) {
if (!read_file(filename, 0)) {
undo_resume();
undo_clear();
widget_browser->rebuild();
fl_message("Can't read %s: %s", filename, strerror(errno));
free((void *)filename);
filename = oldfilename;
@ -706,6 +711,7 @@ void open_history_cb(Fl_Widget *, void *v) {
set_modflag(0, 0);
undo_resume();
undo_clear();
widget_browser->rebuild();
if (oldfilename) {
free((void *)oldfilename);
oldfilename = 0L;
@ -737,6 +743,7 @@ void new_cb(Fl_Widget *, void *v) {
delete_all();
set_filename(NULL);
set_modflag(0, 0);
widget_browser->rebuild();
}
/**
@ -836,6 +843,7 @@ void new_from_template_cb(Fl_Widget *w, void *v) {
}
}
widget_browser->rebuild();
set_modflag(0);
undo_clear();
}
@ -975,7 +983,7 @@ void cut_cb(Fl_Widget *, void *) {
while (p && p->selected) p = p->parent;
delete_all(1);
if (p) select_only(p);
//widget_browser->redraw_lines();
widget_browser->rebuild();
}
/**
@ -993,6 +1001,7 @@ void delete_cb(Fl_Widget *, void *) {
while (p && p->selected) p = p->parent;
delete_all(1);
if (p) select_only(p);
widget_browser->rebuild();
}
/**
@ -1011,9 +1020,12 @@ void paste_cb(Fl_Widget*, void*) {
if (Fl_Type::current && Fl_Type::current->is_group())
strategy = kAddAsLastChild;
if (!read_file(cutfname(), 1, strategy)) {
widget_browser->rebuild();
fl_message("Can't read %s: %s", cutfname(), strerror(errno));
}
undo_resume();
widget_browser->display(Fl_Type::current);
widget_browser->rebuild();
pasteoffset = 0;
ipasteoffset += 10;
force_parent = 0;
@ -1042,6 +1054,8 @@ void duplicate_cb(Fl_Widget*, void*) {
fl_message("Can't read %s: %s", cutfname(1), strerror(errno));
}
fl_unlink(cutfname(1));
widget_browser->display(Fl_Type::current);
widget_browser->rebuild();
undo_resume();
force_parent = 0;
@ -1052,6 +1066,7 @@ void duplicate_cb(Fl_Widget*, void*) {
*/
static void sort_cb(Fl_Widget *,void *) {
sort((Fl_Type*)NULL);
widget_browser->rebuild();
}
/**

View File

@ -82,6 +82,7 @@ void redo_cb(Fl_Widget *, void *) {
undo_suspend();
if (!read_file(undo_filename(undo_current + 1), 0)) {
// Unable to read checkpoint file, don't redo...
widget_browser->rebuild();
undo_resume();
return;
}
@ -90,6 +91,7 @@ void redo_cb(Fl_Widget *, void *) {
// Update modified flag...
set_modflag(undo_current != undo_save);
widget_browser->rebuild();
// Update undo/redo menu items...
if (undo_current >= undo_last) Main_Menu[redo_item].deactivate();
@ -109,18 +111,17 @@ void undo_cb(Fl_Widget *, void *) {
undo_suspend();
// Undo first deletes all widgets which resets the widget_tree browser.
// Save the current scroll position, so we don;t scroll back to 0 at undo.
int x = widget_browser->hposition();
int y = widget_browser->position();
// Save the current scroll position, so we don't scroll back to 0 at undo.
if (widget_browser) widget_browser->save_scroll_position();
if (!read_file(undo_filename(undo_current - 1), 0)) {
// Unable to read checkpoint file, don't undo...
widget_browser->rebuild();
undo_resume();
return;
}
// Restore old browser position.
// Ideally, we would save the browser position insied the undo file.
widget_browser->hposition(x);
widget_browser->position(y);
if (widget_browser) widget_browser->restore_scroll_position();
undo_current --;
@ -130,6 +131,7 @@ void undo_cb(Fl_Widget *, void *) {
// Update undo/redo menu items...
if (undo_current <= 0) Main_Menu[undo_item].deactivate();
Main_Menu[redo_item].activate();
widget_browser->rebuild();
undo_resume();
}

View File

@ -175,9 +175,11 @@ static char *copy_trunc(char *p, const char *str, int maxl, int quote)
\todo It would be nice to be able to grab one or more nodes and mmove them
within the hierarchy.
*/
Widget_Browser::Widget_Browser(int X,int Y,int W,int H,const char*l)
: Fl_Browser_(X,Y,W,H,l),
pushedtitle(NULL)
Widget_Browser::Widget_Browser(int X,int Y,int W,int H,const char*l) :
Fl_Browser_(X,Y,W,H,l),
pushedtitle(NULL),
saved_h_scroll_(0),
saved_v_scroll_(0)
{
type(FL_MULTI_BROWSER);
Fl_Widget::callback(callback_stub);
@ -504,4 +506,70 @@ int Widget_Browser::handle(int e) {
return Fl_Browser_::handle(e);
}
/**
Save the current scrollbar postion during rebuild.
*/
void Widget_Browser::save_scroll_position() {
saved_h_scroll_ = hposition();
saved_v_scroll_ = position();
}
/**
Restore the previous scrollbar postion after rebuild.
*/
void Widget_Browser::restore_scroll_position() {
hposition(saved_h_scroll_);
position(saved_v_scroll_);
}
/**
Rebuild the browser layout to reflect multiple changes.
This clears internal caches, recalculates the scroll bar sizes, and
sends a redraw() request to the widget.
*/
void Widget_Browser::rebuild() {
save_scroll_position();
new_list();
damage(FL_DAMAGE_SCROLL);
redraw();
restore_scroll_position();
}
/**
Rebuild the browser layout and make sure that the given item is visible.
\param[in] inNode pointer to a widget node derived from Fl_Type.
*/
void Widget_Browser::display(Fl_Type *inNode) {
if (!inNode) {
// Alternative: find the first (last?) visible selected item.
return;
}
// remeber our current scroll position
int currentV = position(), newV = currentV;
int nodeV = 0;
// find the inNode in the tree and check, if it is already visible
Fl_Type *p=Fl_Type::first;
for ( ; p && p!=inNode; p=p->next) {
if (p->visible)
nodeV += item_height(p);
}
if (p) {
int xx, yy, ww, hh;
bbox(xx, yy, ww, hh);
int frame_top = xx-x();
int frame_bottom = frame_top + hh;
int node_height = item_height(inNode);
int margin_height = 2 * item_quick_height(inNode);
if (margin_height>hh/2) margin_height = hh/2;
// is the inNode above the current scroll position?
if (nodeV<currentV+margin_height)
newV = nodeV - margin_height;
else if (nodeV>currentV+frame_bottom-margin_height-node_height)
newV = nodeV - frame_bottom + margin_height + node_height;
if (newV<0)
newV = 0;
}
if (newV!=currentV)
position(newV);
}

View File

@ -41,6 +41,8 @@ class Widget_Browser : public Fl_Browser_
}
Fl_Type* pushedtitle;
int saved_h_scroll_;
int saved_v_scroll_;
// required routines for Fl_Browser_ subclass:
void *item_first() const ;
@ -57,7 +59,10 @@ public:
Widget_Browser(int,int,int,int,const char * =NULL);
int handle(int);
void callback();
void deleting(Fl_Type *inType) { Fl_Browser_::deleting((void*)inType); }
void save_scroll_position();
void restore_scroll_position();
void rebuild();
void display(Fl_Type *);
};
#endif // _FLUID_WIDGET_BROWSER_H