xF: add a scrollbar to the buffer list
And clean this area up.
This commit is contained in:
@@ -267,6 +267,8 @@ enum action
|
||||
ACTION_FORMAT_UNDERLINE,
|
||||
ACTION_SCROLL_UP,
|
||||
ACTION_SCROLL_DOWN,
|
||||
ACTION_BUFFER_LIST_SCROLL_UP,
|
||||
ACTION_BUFFER_LIST_SCROLL_DOWN,
|
||||
ACTION_SUBMIT,
|
||||
ACTION_COMPLETE,
|
||||
ACTION_HISTORY_PREVIOUS,
|
||||
@@ -357,8 +359,10 @@ enum app_widget_id
|
||||
WIDGET_NONE,
|
||||
WIDGET_BUTTON,
|
||||
WIDGET_BUFFER,
|
||||
WIDGET_BUFFER_LIST,
|
||||
WIDGET_TRANSCRIPT,
|
||||
WIDGET_SCROLLBAR,
|
||||
WIDGET_BUFFER_LIST_SCROLLBAR,
|
||||
WIDGET_TRANSCRIPT_SCROLLBAR,
|
||||
};
|
||||
|
||||
struct app_backend
|
||||
@@ -452,7 +456,8 @@ static struct
|
||||
struct attrs attrs[ATTRIBUTE_COUNT];
|
||||
struct line_editor editor; ///< Message editor
|
||||
int64_t last_active; ///< Last ACTIVE command timestamp
|
||||
bool dragging_scrollbar; ///< Mouse drag updates the viewport
|
||||
int buffer_list_offset; ///< XUI pixels from the top of the list
|
||||
enum app_widget_id dragged_scrollbar;
|
||||
|
||||
struct binding *keys; ///< Parsed xC-style key bindings
|
||||
size_t keys_len;
|
||||
@@ -882,6 +887,7 @@ app_reset_relay_state (void)
|
||||
{
|
||||
g.buffer_current = NULL;
|
||||
g.buffer_last = NULL;
|
||||
g.buffer_list_offset = 0;
|
||||
|
||||
LIST_FOR_EACH (struct buffer, iter, g.buffers)
|
||||
buffer_destroy (iter);
|
||||
@@ -940,6 +946,8 @@ relay_process_response (const struct relay_event_data_response *e)
|
||||
|
||||
// ~~~ State synchronization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
static void app_buffer_list_ensure_visible (const struct buffer *buffer);
|
||||
|
||||
static struct buffer *
|
||||
buffer_find (const char *name)
|
||||
{
|
||||
@@ -1143,6 +1151,7 @@ relay_process_buffer_activate (struct buffer *b)
|
||||
if (b)
|
||||
{
|
||||
b->scroll_offset = 0;
|
||||
app_buffer_list_ensure_visible (b);
|
||||
app_editor_set (b->input);
|
||||
g.editor.point = MIN (b->input_point, (int) g.editor.len);
|
||||
app_recheck_current_highlight ();
|
||||
@@ -1560,6 +1569,23 @@ relay_reconnect_now (void)
|
||||
|
||||
// --- Viewport policy ---------------------------------------------------------
|
||||
|
||||
static int
|
||||
app_viewport_height (void)
|
||||
{
|
||||
return MAX (0, g_xui.height - 3 * g_xui.vunit);
|
||||
}
|
||||
|
||||
static int
|
||||
app_buffer_list_content_height (void)
|
||||
{
|
||||
size_t count = 0;
|
||||
LIST_FOR_EACH (struct buffer, buffer, g.buffers)
|
||||
count++;
|
||||
|
||||
uint64_t height = count * (uint64_t) g_xui.vunit;
|
||||
return MIN ((uint64_t) INT_MAX, height);
|
||||
}
|
||||
|
||||
static size_t
|
||||
app_visible_line_count (const struct buffer *b)
|
||||
{
|
||||
@@ -1570,37 +1596,6 @@ app_visible_line_count (const struct buffer *b)
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct buffer_line *
|
||||
app_unread_marker_line (const struct buffer *b)
|
||||
{
|
||||
if (!buffer_has_unread_marker (b))
|
||||
return NULL;
|
||||
|
||||
uint64_t unread = (uint64_t) b->new_messages + b->new_unimportant_messages;
|
||||
const struct buffer_line *marker = b->lines_tail;
|
||||
while (unread > 1 && marker->prev)
|
||||
{
|
||||
unread--;
|
||||
marker = marker->prev;
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
static int
|
||||
app_viewport_height (void)
|
||||
{
|
||||
return MAX (0, g_xui.height - 3 * g_xui.vunit);
|
||||
}
|
||||
|
||||
static int
|
||||
app_buffer_content_height (const struct buffer *b)
|
||||
{
|
||||
uint64_t height = app_visible_line_count (b) * (uint64_t) g_xui.vunit;
|
||||
if (buffer_has_unread_marker (b))
|
||||
height += UNREAD_MARKER_HEIGHT;
|
||||
return MIN ((uint64_t) INT_MAX, height);
|
||||
}
|
||||
|
||||
static int
|
||||
app_content_height (void)
|
||||
{
|
||||
@@ -1609,7 +1604,15 @@ app_content_height (void)
|
||||
return MIN ((uint64_t) INT_MAX,
|
||||
g.log.lines.len * (uint64_t) g_xui.vunit);
|
||||
}
|
||||
return g.buffer_current ? app_buffer_content_height (g.buffer_current) : 0;
|
||||
if (g.buffer_current)
|
||||
{
|
||||
struct buffer *b = g.buffer_current;
|
||||
uint64_t height = app_visible_line_count (b) * (uint64_t) g_xui.vunit;
|
||||
if (buffer_has_unread_marker (b))
|
||||
height += UNREAD_MARKER_HEIGHT;
|
||||
return MIN ((uint64_t) INT_MAX, height);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int *
|
||||
@@ -1617,36 +1620,102 @@ app_content_scroll_offset (void)
|
||||
{
|
||||
if (g.log.visible)
|
||||
return &g.log.offset;
|
||||
return g.buffer_current ? &g.buffer_current->scroll_offset : NULL;
|
||||
if (g.buffer_current)
|
||||
return &g.buffer_current->scroll_offset;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
struct app_scroll_view
|
||||
{
|
||||
int *offset; ///< Pixels from the anchored edge
|
||||
int total; ///< Total content height in pixels
|
||||
bool from_bottom; ///< The offset is bottom-anchored
|
||||
};
|
||||
|
||||
static struct app_scroll_view
|
||||
app_scroll_view (enum app_widget_id id)
|
||||
{
|
||||
if (id == WIDGET_BUFFER_LIST_SCROLLBAR)
|
||||
return (struct app_scroll_view) { &g.buffer_list_offset,
|
||||
app_buffer_list_content_height (), false };
|
||||
|
||||
hard_assert (id == WIDGET_TRANSCRIPT_SCROLLBAR);
|
||||
return (struct app_scroll_view) { app_content_scroll_offset (),
|
||||
app_content_height (), true };
|
||||
}
|
||||
|
||||
static int
|
||||
app_content_top (void)
|
||||
app_scroll_view_origin (struct app_scroll_view *self)
|
||||
{
|
||||
int total = app_content_height ();
|
||||
int *offset = app_content_scroll_offset ();
|
||||
return MAX (0, total - app_viewport_height () - (offset ? *offset : 0));
|
||||
if (!self->offset)
|
||||
return 0;
|
||||
|
||||
int maximum = MAX (0, self->total - app_viewport_height ());
|
||||
*self->offset = MAX (0, MIN (maximum, *self->offset));
|
||||
return self->from_bottom
|
||||
? self->total - app_viewport_height () - *self->offset
|
||||
: *self->offset;
|
||||
}
|
||||
|
||||
static bool
|
||||
app_scroll (int amount)
|
||||
app_scroll_view_set_origin (struct app_scroll_view *self, int64_t origin)
|
||||
{
|
||||
int *offset = app_content_scroll_offset ();
|
||||
if (!offset)
|
||||
if (!self->offset)
|
||||
return false;
|
||||
|
||||
int maximum = MAX (0, app_content_height () - app_viewport_height ());
|
||||
int64_t requested = (int64_t) *offset + amount;
|
||||
int updated = MAX (0, MIN ((int64_t) maximum, requested));
|
||||
if (updated == *offset)
|
||||
int maximum = MAX (0, self->total - app_viewport_height ());
|
||||
int updated = MAX (0, MIN ((int64_t) maximum, origin));
|
||||
if (self->from_bottom)
|
||||
updated = maximum - updated;
|
||||
if (updated == *self->offset)
|
||||
return false;
|
||||
|
||||
*offset = updated;
|
||||
app_recheck_current_highlight ();
|
||||
*self->offset = updated;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
app_scroll (enum app_widget_id id, int amount)
|
||||
{
|
||||
struct app_scroll_view view = app_scroll_view (id);
|
||||
if (!app_scroll_view_set_origin (&view,
|
||||
(int64_t) app_scroll_view_origin (&view) + amount))
|
||||
return false;
|
||||
|
||||
if (view.from_bottom)
|
||||
app_recheck_current_highlight ();
|
||||
xui_invalidate ();
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
app_buffer_list_ensure_visible (const struct buffer *buffer)
|
||||
{
|
||||
int visible = app_viewport_height ();
|
||||
if (visible <= 0 || g_xui.vunit <= 0)
|
||||
return;
|
||||
|
||||
int64_t row_top = 0;
|
||||
struct buffer *iter;
|
||||
for (iter = g.buffers; iter && iter != buffer; iter = iter->next)
|
||||
row_top += g_xui.vunit;
|
||||
if (!iter)
|
||||
return;
|
||||
|
||||
struct app_scroll_view view =
|
||||
app_scroll_view (WIDGET_BUFFER_LIST_SCROLLBAR);
|
||||
int64_t origin = app_scroll_view_origin (&view);
|
||||
if (row_top < origin)
|
||||
origin = row_top;
|
||||
else if (row_top + g_xui.vunit > origin + visible)
|
||||
origin = row_top + g_xui.vunit - visible;
|
||||
app_scroll_view_set_origin (&view, origin);
|
||||
}
|
||||
|
||||
// --- Terminal widgets --------------------------------------------------------
|
||||
|
||||
static void
|
||||
@@ -1835,6 +1904,25 @@ app_select_backend (void)
|
||||
#endif // WITH_X11
|
||||
}
|
||||
|
||||
static struct widget *
|
||||
app_make_scrollbar (enum app_widget_id id)
|
||||
{
|
||||
struct app_scroll_view view = app_scroll_view (id);
|
||||
int top = MAX (0, app_scroll_view_origin (&view));
|
||||
if (app_viewport_height () <= 0
|
||||
|| view.total <= app_viewport_height ())
|
||||
return NULL;
|
||||
|
||||
struct widget *scrollbar = g_xui.ui->scrollbar
|
||||
(g_attrs[ATTRIBUTE_SCROLLBAR].attrs, top, view.total);
|
||||
#ifdef WITH_X11
|
||||
if (g_xui.ui == &x11_ui)
|
||||
scrollbar->on_render = app_x11_render_scrollbar;
|
||||
#endif // WITH_X11
|
||||
scrollbar->widget_id = id;
|
||||
return scrollbar;
|
||||
}
|
||||
|
||||
// --- Layout and rendering ----------------------------------------------------
|
||||
|
||||
struct layout
|
||||
@@ -2059,18 +2147,53 @@ app_make_buffer_row (struct buffer *b, int index)
|
||||
return widget;
|
||||
}
|
||||
|
||||
static void
|
||||
app_on_viewport_allocated (struct widget *self)
|
||||
{
|
||||
xui_on_vbox_allocated (self);
|
||||
|
||||
int y = self->y + self->userdata;
|
||||
LIST_FOR_EACH (struct widget, child, self->children)
|
||||
{
|
||||
widget_move (child, self->x - child->x, y - child->y);
|
||||
y += child->height;
|
||||
}
|
||||
}
|
||||
|
||||
static struct widget *
|
||||
app_make_buffer_list (void)
|
||||
{
|
||||
struct layout rows = {};
|
||||
int index = 0;
|
||||
struct app_scroll_view view =
|
||||
app_scroll_view (WIDGET_BUFFER_LIST_SCROLLBAR);
|
||||
int top = app_scroll_view_origin (&view);
|
||||
int bottom = top + app_viewport_height ();
|
||||
int index = 0, first = top / g_xui.vunit;
|
||||
LIST_FOR_EACH (struct buffer, buffer, g.buffers)
|
||||
app_push (&rows, app_make_buffer_row (buffer, index++));
|
||||
{
|
||||
int row_top = index * g_xui.vunit;
|
||||
if (index >= first && row_top < bottom)
|
||||
app_push (&rows, app_make_buffer_row (buffer, index));
|
||||
index++;
|
||||
}
|
||||
|
||||
struct widget *list = xui_vbox (rows.head);
|
||||
list->width = MIN (20 * g_xui.hunit, g_xui.width / 3);
|
||||
list->height = -1;
|
||||
return list;
|
||||
list->on_allocated = app_on_viewport_allocated;
|
||||
list->userdata = -(top % g_xui.vunit);
|
||||
list->width = list->height = -1;
|
||||
|
||||
struct layout viewport = {};
|
||||
app_push (&viewport, list);
|
||||
struct widget *scrollbar =
|
||||
app_make_scrollbar (WIDGET_BUFFER_LIST_SCROLLBAR);
|
||||
if (scrollbar)
|
||||
app_push (&viewport, scrollbar);
|
||||
|
||||
struct widget *viewport_box = xui_hbox (viewport.head);
|
||||
viewport_box->widget_id = WIDGET_BUFFER_LIST;
|
||||
viewport_box->width = MIN (20 * g_xui.hunit, g_xui.width / 3);
|
||||
viewport_box->height = -1;
|
||||
return viewport_box;
|
||||
}
|
||||
|
||||
static enum app_attribute
|
||||
@@ -2200,20 +2323,15 @@ struct transcript_slice
|
||||
static struct transcript_slice
|
||||
app_make_transcript_slice (void)
|
||||
{
|
||||
int *scroll_offset = app_content_scroll_offset ();
|
||||
hard_assert (scroll_offset);
|
||||
|
||||
int total = app_content_height ();
|
||||
struct app_scroll_view view =
|
||||
app_scroll_view (WIDGET_TRANSCRIPT_SCROLLBAR);
|
||||
int viewport = app_viewport_height ();
|
||||
int maximum = MAX (0, total - viewport);
|
||||
*scroll_offset = MAX (0, MIN (*scroll_offset, maximum));
|
||||
|
||||
int view_top = total - viewport - *scroll_offset;
|
||||
int view_top = app_scroll_view_origin (&view);
|
||||
return (struct transcript_slice)
|
||||
{
|
||||
.view_top = view_top,
|
||||
.clip_top = MAX (0, view_top),
|
||||
.clip_bottom = MIN (total, view_top + viewport),
|
||||
.clip_bottom = MIN (view.total, view_top + viewport),
|
||||
.first_y = -1,
|
||||
};
|
||||
}
|
||||
@@ -2251,6 +2369,22 @@ app_append_log_rows (struct layout *rows)
|
||||
return app_transcript_slice_offset (&slice);
|
||||
}
|
||||
|
||||
static const struct buffer_line *
|
||||
app_unread_marker_line (const struct buffer *b)
|
||||
{
|
||||
if (!buffer_has_unread_marker (b))
|
||||
return NULL;
|
||||
|
||||
uint64_t unread = (uint64_t) b->new_messages + b->new_unimportant_messages;
|
||||
const struct buffer_line *marker = b->lines_tail;
|
||||
while (unread > 1 && marker->prev)
|
||||
{
|
||||
unread--;
|
||||
marker = marker->prev;
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
static int
|
||||
app_append_buffer_rows (struct layout *rows)
|
||||
{
|
||||
@@ -2274,19 +2408,6 @@ app_append_buffer_rows (struct layout *rows)
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
app_on_transcript_allocated (struct widget *self)
|
||||
{
|
||||
xui_on_vbox_allocated (self);
|
||||
|
||||
int y = self->y + self->userdata;
|
||||
LIST_FOR_EACH (struct widget, child, self->children)
|
||||
{
|
||||
widget_move (child, self->x - child->x, y - child->y);
|
||||
y += child->height;
|
||||
}
|
||||
}
|
||||
|
||||
static struct widget *
|
||||
app_make_transcript (void)
|
||||
{
|
||||
@@ -2296,7 +2417,7 @@ app_make_transcript (void)
|
||||
: app_append_buffer_rows (&rows);
|
||||
|
||||
struct widget *transcript = xui_vbox (rows.head);
|
||||
transcript->on_allocated = app_on_transcript_allocated;
|
||||
transcript->on_allocated = app_on_viewport_allocated;
|
||||
transcript->widget_id = WIDGET_TRANSCRIPT;
|
||||
transcript->userdata = offset;
|
||||
transcript->width = transcript->height = -1;
|
||||
@@ -2311,19 +2432,9 @@ app_make_middle (void)
|
||||
|
||||
struct layout viewport = {};
|
||||
app_push (&viewport, app_make_transcript ());
|
||||
int viewport_height = app_viewport_height ();
|
||||
if (viewport_height > 0 && app_content_height () > viewport_height)
|
||||
{
|
||||
struct widget *scrollbar = g_xui.ui->scrollbar
|
||||
(g_attrs[ATTRIBUTE_SCROLLBAR].attrs,
|
||||
app_content_top (), app_content_height ());
|
||||
#ifdef WITH_X11
|
||||
if (g_xui.ui == &x11_ui)
|
||||
scrollbar->on_render = app_x11_render_scrollbar;
|
||||
#endif // WITH_X11
|
||||
scrollbar->widget_id = WIDGET_SCROLLBAR;
|
||||
struct widget *scrollbar = app_make_scrollbar (WIDGET_TRANSCRIPT_SCROLLBAR);
|
||||
if (scrollbar)
|
||||
app_push (&viewport, scrollbar);
|
||||
}
|
||||
|
||||
struct widget *viewport_box = xui_hbox (viewport.head);
|
||||
viewport_box->width = viewport_box->height = -1;
|
||||
@@ -2594,19 +2705,15 @@ app_scrollbar_seek (struct widget *scrollbar, int y)
|
||||
if (!scrollbar || scrollbar->height <= 0)
|
||||
return false;
|
||||
|
||||
int total = app_content_height ();
|
||||
int visible = app_viewport_height ();
|
||||
int maximum = MAX (0, total - visible);
|
||||
struct app_scroll_view view = app_scroll_view (scrollbar->widget_id);
|
||||
int top = (double) MAX (0, MIN (scrollbar->height, y))
|
||||
/ scrollbar->height * total - visible / 2;
|
||||
top = MAX (0, MIN (maximum, top));
|
||||
|
||||
int *offset = app_content_scroll_offset ();
|
||||
if (!offset)
|
||||
/ scrollbar->height * view.total - app_viewport_height () / 2;
|
||||
if (!view.offset)
|
||||
return false;
|
||||
|
||||
*offset = maximum - top;
|
||||
app_recheck_current_highlight ();
|
||||
app_scroll_view_set_origin (&view, top);
|
||||
if (view.from_bottom)
|
||||
app_recheck_current_highlight ();
|
||||
xui_invalidate ();
|
||||
return true;
|
||||
}
|
||||
@@ -2844,9 +2951,13 @@ app_process_action (enum action action)
|
||||
app_editor_insert (0x1f);
|
||||
return true;
|
||||
case ACTION_SCROLL_UP:
|
||||
return app_scroll (+page);
|
||||
return app_scroll (WIDGET_TRANSCRIPT_SCROLLBAR, -page);
|
||||
case ACTION_SCROLL_DOWN:
|
||||
return app_scroll (-page);
|
||||
return app_scroll (WIDGET_TRANSCRIPT_SCROLLBAR, +page);
|
||||
case ACTION_BUFFER_LIST_SCROLL_UP:
|
||||
return app_scroll (WIDGET_BUFFER_LIST_SCROLLBAR, -page);
|
||||
case ACTION_BUFFER_LIST_SCROLL_DOWN:
|
||||
return app_scroll (WIDGET_BUFFER_LIST_SCROLLBAR, +page);
|
||||
case ACTION_SUBMIT:
|
||||
app_submit_input ();
|
||||
return true;
|
||||
@@ -2875,15 +2986,16 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
|
||||
app_notify_active ();
|
||||
if (type == TERMO_MOUSE_RELEASE)
|
||||
{
|
||||
g.dragging_scrollbar = false;
|
||||
g.dragged_scrollbar = WIDGET_NONE;
|
||||
return true;
|
||||
}
|
||||
if (type == TERMO_MOUSE_DRAG)
|
||||
{
|
||||
if (!g.dragging_scrollbar)
|
||||
if (!g.dragged_scrollbar)
|
||||
return true;
|
||||
|
||||
struct widget *scrollbar =
|
||||
app_widget_by_id (g_xui.widgets, WIDGET_SCROLLBAR);
|
||||
app_widget_by_id (g_xui.widgets, g.dragged_scrollbar);
|
||||
return app_scrollbar_seek (scrollbar,
|
||||
scrollbar ? y - scrollbar->y : 0);
|
||||
}
|
||||
@@ -2892,18 +3004,26 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
|
||||
|
||||
if (button == 4 || button == 5)
|
||||
{
|
||||
int amount = (button == 4 ? -1 : 1) * 3 * g_xui.vunit;
|
||||
|
||||
struct widget *buffer_list =
|
||||
app_widget_by_id (g_xui.widgets, WIDGET_BUFFER_LIST);
|
||||
if (buffer_list && app_widget_contains (buffer_list, x, y))
|
||||
return app_scroll (WIDGET_BUFFER_LIST_SCROLLBAR, amount);
|
||||
|
||||
struct widget *transcript =
|
||||
app_widget_by_id (g_xui.widgets, WIDGET_TRANSCRIPT);
|
||||
return transcript && app_widget_contains (transcript, x, y)
|
||||
&& app_scroll ((button == 4 ? 1 : -1) * 3 * g_xui.vunit);
|
||||
&& app_scroll (WIDGET_TRANSCRIPT_SCROLLBAR, amount);
|
||||
}
|
||||
if (button != 1)
|
||||
return false;
|
||||
|
||||
struct widget *target = app_interactive_widget_at (g_xui.widgets, x, y);
|
||||
if (target && target->widget_id == WIDGET_SCROLLBAR)
|
||||
if (target && (target->widget_id == WIDGET_BUFFER_LIST_SCROLLBAR
|
||||
|| target->widget_id == WIDGET_TRANSCRIPT_SCROLLBAR))
|
||||
{
|
||||
g.dragging_scrollbar = true;
|
||||
g.dragged_scrollbar = target->widget_id;
|
||||
return app_scrollbar_seek (target, y - target->y);
|
||||
}
|
||||
if (target && target->widget_id == WIDGET_BUTTON)
|
||||
@@ -2951,6 +3071,8 @@ g_default_bindings[] =
|
||||
{ "M-m", ACTION_FORMAT },
|
||||
{ "PageUp", ACTION_SCROLL_UP },
|
||||
{ "PageDown", ACTION_SCROLL_DOWN },
|
||||
{ "S-PageUp", ACTION_BUFFER_LIST_SCROLL_UP },
|
||||
{ "S-PageDown", ACTION_BUFFER_LIST_SCROLL_DOWN },
|
||||
{ "Enter", ACTION_SUBMIT },
|
||||
{ "C-j", ACTION_SUBMIT },
|
||||
{ "Tab", ACTION_COMPLETE },
|
||||
|
||||
Reference in New Issue
Block a user