diff --git a/include/tim.h b/include/tim.h index 0170867..e02765f 100644 --- a/include/tim.h +++ b/include/tim.h @@ -170,22 +170,31 @@ typedef struct TimEditState { } TimEditState; -typedef struct TimScrollItem { - void* data; +typedef struct TimPanelItem { + // Size of item to know where to draw next item. + // Set to to A and items will be spread equally across panel + i32 w; + i32 h; + void* data; // is passed to draw() void* focus_target; // is assigned to tim->focus - i32 h; // height of the item to know where to draw next item - void (*draw)(bool is_selected, TimRect place, void* data); -} TimScrollItem; + void (*draw)(void* data, TimRect place, bool is_selected); +} TimPanelItem; -typedef struct TimScrollState { - TimScrollItem* items; - i32 len; - i32 cur; +typedef struct TimPanel { + TimPanelItem* items; // array + i32 len; // number of items + i32 cur; // index of current item + i32 spacing; // distance between items bool draw_border; - bool show_scroll_bar_vertical; - bool use_mouse_wheel; -} TimScrollState; + bool is_horizontal; +} TimPanel; +typedef struct TimScrollView { + i32 offset; + i32 content_h; + void* data; // is passed to draw() + void (*draw)(void* data, TimRect place); +} TimScrollView; typedef struct TimState { i32 w; // screen width @@ -308,6 +317,7 @@ bool tim_checkbox(cstr txt, i32* state, i32 x, i32 y, i32 w, TimStyle style); // color: radio, background, text bool tim_radiobutton(cstr txt, i32* state, i32 v, i32 x, i32 y, i32 w, TimStyle style); + /// text edit - value in state /// @param e persistent edit state, use TimEditState_construct() to create new state /// @param style frame, background, text @@ -319,16 +329,22 @@ TimKey tim_edit(TimEditState* e, i32 x, i32 y, i32 w, TimStyle style); /// @param capacity buffer size in bytes /// @param initial_content may be NULL void TimEditState_construct(TimEditState* e, char* buffer, i32 capacity, cstr initial_content); - void TimEditState_insert(TimEditState* e, cstr s); void TimEditState_delete(TimEditState* e); -/// @param l list of rows to display -/// @param style frame, background, text -TimScrollItem* tim_scroll(TimScrollState* l, i32 x, i32 y, i32 w, i32 h, TimStyle style); -void TimScrollState_selectNext(TimScrollState* l); -void TimScrollState_selectPrev(TimScrollState* l); +/// Panel with sequence of items. You can select an item by arrow keys or mouse click. +/// @param self persistent state +/// @param is_selected if panel is not selected, it calls items[:]->draw(is_selected=false) +/// @param style frame, background, text +/// @return current item +TimPanelItem* tim_panel(TimPanel* self, bool is_selected, i32 x, i32 y, i32 w, i32 h, TimStyle style); + +void TimPanel_selectNext(TimPanel* self); +void TimPanel_selectPrev(TimPanel* self); + +/// +void tim_scroll_view(TimScrollView* self, i32 x, i32 y, i32 w, i32 h, TimStyle style); #pragma endregion diff --git a/src/drawing.c b/src/drawing.c index aa1079a..37aab0f 100755 --- a/src/drawing.c +++ b/src/drawing.c @@ -85,6 +85,8 @@ void tim_draw_str(cstr s, i32 x, i32 y, i32 w, u8 fg, u8 bg) { } void tim_draw_box(i32 x, i32 y, i32 w, i32 h, u8 fg, u8 bg) { + if(w <= 0 || h <= 0) + return; tim_draw_chr(tim_cell("┌", fg, bg), x , y); tim_draw_chr(tim_cell("┐", fg, bg), x + w - 1, y); tim_draw_chr(tim_cell("└", fg, bg), x , y + h - 1); diff --git a/src/panel.c b/src/panel.c new file mode 100755 index 0000000..c3c8d6c --- /dev/null +++ b/src/panel.c @@ -0,0 +1,95 @@ +#include "tim.h" + +void TimPanel_selectNext(TimPanel* l){ + if(l->cur + 1 < l->len) + l->cur++; +} + +void TimPanel_selectPrev(TimPanel* l){ + if(l->cur - 1 >= 0) + l->cur--; +} + +TimPanelItem* tim_panel(TimPanel* self, bool is_panel_selected, i32 x, i32 y, i32 w, i32 h, TimStyle style){ + // select item with keyboard + if(tim_is_key_press(self->is_horizontal ? TimKey_Left : TimKey_Up)) + { + TimPanel_selectPrev(self); + } + if(tim_is_key_press(self->is_horizontal ? TimKey_Right : TimKey_Down)) + { + TimPanel_selectNext(self); + } + + // set focus on current item + tim->focus = self->items[self->cur].focus_target; + + tim_scope(x, y, w, h) + { + TimRect content_scope = tim->scopes[tim->scope]; + + // draw border and shrink items_scope + if(self->draw_border){ + tim_frame(0, 0, ~0, ~0, style); + // put current scope inside frame + content_scope.x += 1; + content_scope.y += 1; + content_scope.w -= 2; + content_scope.h -= 2; + tim->scopes[tim->scope] = content_scope; + } + + // draw items + TimRect item_place = { 0 }; + for(i32 i = 0; i < self->len; i++){ + TimPanelItem* item = &self->items[i]; + + item_place.w = item->w; + if(item_place.w == A){ + if(self->is_horizontal){ + item_place.w = content_scope.w / self->len; + // add remaining width to the last item + if(i == self->len - 1) + item_place.w += content_scope.w % self->len; + } + else { + item_place.w = content_scope.w; + } + } + + item_place.h = item->h; + if(item_place.h == A){ + if(self->is_horizontal){ + item_place.h = content_scope.h; + } + else { + item_place.h = content_scope.h / self->len; + // add remaining height to the last item + if(i == self->len - 1) + item_place.h += content_scope.h % self->len; + } + } + + // select item with mouse click + if(tim_is_mouse_click_over(tim_scope_rect_to_absolute(item_place.x, item_place.y, item_place.w, item_place.h))){ + self->cur = i; + tim->focus = item->focus_target; + } + + bool is_item_selected = false; + if(is_panel_selected) + is_item_selected = i == self->cur; + item->draw(item->data, item_place, is_item_selected); + + // adjust place for next item + if(self->is_horizontal){ + item_place.x += item_place.w; + } + else { + item_place.y += item_place.h + self->spacing; + } + } + } + + return &self->items[self->cur]; +} diff --git a/src/scroll.c b/src/scroll.c deleted file mode 100755 index f0c2210..0000000 --- a/src/scroll.c +++ /dev/null @@ -1,115 +0,0 @@ -#include "tim.h" -#include - -void TimScrollState_selectNext(TimScrollState* l){ - if(l->cur + 1 < l->len) - l->cur++; -} - -void TimScrollState_selectPrev(TimScrollState* l){ - if(l->cur - 1 >= 0) - l->cur--; -} - -TimScrollItem* tim_scroll(TimScrollState* l, i32 x, i32 y, i32 w, i32 h, TimStyle style){ - // select row with keyboard and mouse wheel - if(tim_is_key_press(TimKey_Down) - || (l->use_mouse_wheel && tim_is_mouse_scroll_down())) - { - TimScrollState_selectNext(l); - } - if(tim_is_key_press(TimKey_Up) - || (l->use_mouse_wheel && tim_is_mouse_scroll_up())) - { - TimScrollState_selectPrev(l); - } - - tim->focus = l->items[l->cur].focus_target; - - tim_scope(x, y, w, h) - { - TimRect items_scope = tim->scopes[tim->scope]; - - // draw border and shrink items_scope - if(l->draw_border){ - tim_frame(0, 0, ~0, ~0, style); - items_scope.x += 1; - items_scope.y += 1; - items_scope.w -= 2; - items_scope.h -= 2; - } - - // draw scroll bar and shrink items_scope - if(l->show_scroll_bar_vertical){ - items_scope.w -= 1; - TimRect arrow_up_r = { - .x = items_scope.x + items_scope.w, - .y = items_scope.y, - .w = 1, - .h = 1 - }; - TimRect scrollbar_r = { - .x = arrow_up_r.x, - .y = arrow_up_r.y + 1, - .w = 1, - .h = items_scope.h - 2 - }; - TimRect arrow_down_r = { - .x = scrollbar_r.x, - .y = scrollbar_r.y + scrollbar_r.h, - .w = 1, - .h = 1 - }; - - if (tim->event.type == TimEvent_Draw) { - f32 scroll_ratio = 0; - i32 slider_h = 0; - if(l->len != 0){ - scroll_ratio = (f32)l->cur / l->len + 0.001f; - slider_h = ceilf((f32)scrollbar_r.h / l->len); - } - i32 slider_y = scrollbar_r.y + scrollbar_r.h * scroll_ratio; - - tim_draw_chr(tim_cell("▲", style.brd, style.bg), arrow_up_r.x, arrow_up_r.y); - tim_draw_col(tim_cell("░", style.brd, style.bg), scrollbar_r.x, scrollbar_r.y, scrollbar_r.h); - tim_draw_col(tim_cell("█", style.brd, style.bg), scrollbar_r.x, slider_y, slider_h); - tim_draw_chr(tim_cell("▼", style.brd, style.bg), arrow_down_r.x, arrow_down_r.y); - } - - if(tim_is_mouse_click_over(arrow_up_r)){ - TimScrollState_selectPrev(l); - } - if(tim_is_mouse_click_over(arrow_down_r)){ - TimScrollState_selectNext(l); - } - if(tim_is_mouse_click_over(scrollbar_r)){ - i32 click_y_rel = tim->event.y - scrollbar_r.y; - f32 scroll_ratio = 0; - if(l->len != 0){ - scroll_ratio = (f32)click_y_rel / scrollbar_r.h + 0.001f; - l->cur = l->len * scroll_ratio; - } - } - } - - tim->scopes[tim->scope] = items_scope; // fit current scope inside frame and scrollbar - - // draw items - TimRect item_place = { .x = 0, .y = 0, .w = items_scope.w, .h = 0 }; - for(u32 i = 0; i < l->len; i++){ - TimScrollItem* item = &l->items[i]; - item_place.h = item->h; - - // select row with mouse click - if(tim_is_mouse_click_over(tim_scope_rect_to_absolute(item_place.x, item_place.y, item_place.w, item_place.h))){ - l->cur = i; - tim->focus = item->focus_target; - } - - item->draw(i == l->cur, item_place, item->data); - item_place.y += item_place.h; - } - } - - return &l->items[l->cur]; -} diff --git a/src/scroll_view.c b/src/scroll_view.c new file mode 100644 index 0000000..d86d994 --- /dev/null +++ b/src/scroll_view.c @@ -0,0 +1,68 @@ +#include "tim.h" +#include + +void tim_scroll_view(TimScrollView* self, i32 x, i32 y, i32 w, i32 h, TimStyle style){ + tim_scope(x, y, w, h) + { + TimRect content_scope = tim->scopes[tim->scope]; + // shrink content_scope to put scrollbar + content_scope.w -= 1; + tim->scopes[tim->scope] = content_scope; + + // draw scroll bar and + TimRect arrow_up_r = { + .x = content_scope.x + content_scope.w, + .y = content_scope.y, + .w = 1, + .h = 1 + }; + TimRect scrollbar_r = { + .x = arrow_up_r.x, + .y = arrow_up_r.y + 1, + .w = 1, + .h = content_scope.h - 2 + }; + TimRect arrow_down_r = { + .x = scrollbar_r.x, + .y = scrollbar_r.y + scrollbar_r.h, + .w = 1, + .h = 1 + }; + + if (tim->event.type == TimEvent_Draw) { + f32 scroll_ratio = 0; + i32 slider_h = 0; + if(self->content_h != 0){ + scroll_ratio = (f32)self->offset / self->content_h + 0.001f; + slider_h = ceilf((f32)scrollbar_r.h / self->content_h); + } + i32 slider_y = scrollbar_r.y + scrollbar_r.h * scroll_ratio; + + tim_draw_chr(tim_cell("▲", style.brd, style.bg), arrow_up_r.x, arrow_up_r.y); + tim_draw_col(tim_cell("░", style.brd, style.bg), scrollbar_r.x, scrollbar_r.y, scrollbar_r.h); + tim_draw_col(tim_cell("█", style.brd, style.bg), scrollbar_r.x, slider_y, slider_h); + tim_draw_chr(tim_cell("▼", style.brd, style.bg), arrow_down_r.x, arrow_down_r.y); + } + + if(tim_is_mouse_click_over(arrow_up_r)){ + if(self->offset - 1 >= 0) + self->offset--; + } + if(tim_is_mouse_click_over(arrow_down_r)){ + if(self->offset + 1 < self->content_h) + self->offset++; + } + if(tim_is_mouse_click_over(scrollbar_r)){ + i32 click_y_rel = tim->event.y - scrollbar_r.y; + f32 scroll_ratio = 0; + if(scrollbar_r.h != 0){ + scroll_ratio = (f32)click_y_rel / scrollbar_r.h + 0.001f; + self->offset = self->content_h * scroll_ratio; + } + } + + // draw content + TimRect content_place = { .x = 0, .y = 0, .w = content_scope.w, .h = content_scope.h }; + self->draw(self->data, content_place); + } +} \ No newline at end of file