diff options
| author | Matthias Melcher <github@matthiasm.com> | 2023-11-05 20:04:43 +0100 |
|---|---|---|
| committer | Matthias Melcher <github@matthiasm.com> | 2023-11-05 20:04:49 +0100 |
| commit | 1f5472a7d37af7909c1daa37bb5aee7296df170a (patch) | |
| tree | 9ff3fde1ce50587c5ce3b27e55593cb8f27dc788 /fluid | |
| parent | 0ae927a00e9ecd45a45edf004e939ab2e5ce4813 (diff) | |
FLUID: Adds transient cells to Fl_Grid
This allows multiple widgets to occupy a single cell which is
needed when moving cells across the grid interactively.
Diffstat (limited to 'fluid')
| -rw-r--r-- | fluid/Fl_Grid_Type.cxx | 263 | ||||
| -rw-r--r-- | fluid/Fl_Grid_Type.h | 15 | ||||
| -rw-r--r-- | fluid/alignment_panel.fl | 32 | ||||
| -rw-r--r-- | fluid/documentation/src-dev/page_introduction.dox | 19 |
4 files changed, 290 insertions, 39 deletions
diff --git a/fluid/Fl_Grid_Type.cxx b/fluid/Fl_Grid_Type.cxx index 33c061291..493565b6a 100644 --- a/fluid/Fl_Grid_Type.cxx +++ b/fluid/Fl_Grid_Type.cxx @@ -32,9 +32,34 @@ #include <stdio.h> #include <stdlib.h> +#include <assert.h> // ---- Fl_Grid_Proxy --------------------------------------------------- MARK: - +/** + An implementation of the Fl_Grid widget with additional functionality. + + Fl_Grid_Proxy add a list of transient children, i.e. children that are + temporarily assigned to a cell that is already taken by another child. + */ +Fl_Grid_Proxy::Fl_Grid_Proxy(int X,int Y,int W,int H) +: Fl_Grid(X,Y,W,H), + transient_(NULL), + num_transient_(0), + cap_transient_(0) +{ +} + +Fl_Grid_Proxy::~Fl_Grid_Proxy() { + int i; + if (transient_) { + for (i=0; i<num_transient_; i++) { + if (transient_[i].cell) ::free(transient_[i].cell); + } + ::free(transient_); + } +} + // Override group's resize behavior to do nothing to children: void Fl_Grid_Proxy::resize(int X, int Y, int W, int H) { if (Fl_Type::allow_layout > 0) { @@ -65,6 +90,183 @@ void Fl_Grid_Proxy::draw_overlay() { fl_color(grid_color); } +/** + Move a cell into the grid or within the grid. + + \param[in] in_child must already be a child of grid + \param[in] to_row, to_col move the child into this cell + \param[in] how 0: replace occupant, 1: don't replace, 2 = make transient + if occupied + + \todo If the target cell is already taken, this function will remove the old + child from the cell and leave it in limbo (a child of grid, but with no + assigned cell). As this function is used to move children around with arrow + keys, it must be possible to move a widget past a cell that is already taken + without disturbing the existing system. + + One solution would be to make the widget "hover", meaning, it will be registered + in Fl_Grid_Proxy as using the same cell as another widget. I am not sure at all + how to implement that yet. We could return the original cell, but that may have + different minsize, etc. . It would be better to have a temporary cell to store + all the information. + + We must make sure that a hovering cell (or multiple cells) are a very temporary + solution, because all this information will be skewed if Grid::layout is + called, and all information is lost when we save the file for compilation. + + We must show a conflict grid under or around the hovering cell! + + Also mind the rowspan and colspan! + */ +void Fl_Grid_Proxy::move_cell(Fl_Widget *in_child, int to_row, int to_col, int how) { + // the child must already be a true child of grid + assert(find(in_child)<children()); + + short rowspan = 1, colspan = 1; + Fl_Grid_Align align = FL_GRID_FILL; + int w = 20, h = 20; + const Fl_Grid::Cell *old_cell = cell(in_child); + if (old_cell) { + rowspan = old_cell->rowspan(); + colspan = old_cell->colspan(); + align = old_cell->align(); + old_cell->minimum_size(&w, &h); + } + if ((to_row < 0) || (to_row+rowspan > rows())) return; + if ((to_col < 0) || (to_col+colspan > cols())) return; + Fl_Grid::Cell *new_cell = NULL; + if (how == 0) { // replace old occupant in cell, making that one homeless + new_cell = widget(in_child, to_row, to_col, rowspan, colspan, align); + } else if (how == 1) { // don't replace an old occupant, making ourselves homeless + // todo: colspan, rowspan? + if (cell(to_row, to_col) == NULL) { + new_cell = widget(in_child, to_row, to_col, rowspan, colspan, align); + } else { + if (old_cell) remove_cell(old_cell->row(), old_cell->col()); + } + } else if (how == 2) { + Cell *current = cell(to_row, to_col); + if (current == NULL) { + new_cell = widget(in_child, to_row, to_col, rowspan, colspan, align); + } else { + if (old_cell) remove_cell(old_cell->row(), old_cell->col()); + new_cell = transient_widget(in_child, to_row, to_col, rowspan, colspan, align); + Fl_Widget *w = current->widget(); + Fl_Type::allow_layout++; + in_child->resize(w->x(), w->y(), w->w(), w->h()); + Fl_Type::allow_layout--; + } + } + if (new_cell) new_cell->minimum_size(w, h); +} + +/** + Generate or replace a transient widget entry. + If the widget is in the cell list, it will be removed there. + If the widget is already transient, the cell will be replaced. + \param[in] wi a child of this Fl_Grid_Proxy, that may be linked to a cell or transient cell + \param[in] row, col, row_span, col_span, align cell parameters + */ +Fl_Grid::Cell* Fl_Grid_Proxy::transient_widget(Fl_Widget *wi, int row, int col, int row_span, int col_span, Fl_Grid_Align align) { + int i; + bool remove_old_cell = false; + Cell *old_cell = cell(wi); + if (old_cell) { + remove_old_cell = true; + } else { + for (i=0; i<num_transient_; i++) { + if (transient_[i].widget == wi) { + old_cell = transient_[i].cell; + break; + } + } + } + Cell *new_cell = new Cell(wi, row, col); + new_cell->rowspan(row_span); + new_cell->colspan(col_span); + new_cell->align(align); + if (old_cell) { + int mw, mh; + old_cell->minimum_size(&mw, &mh); + new_cell->minimum_size(mw, mh); + ::free(old_cell); + } + if (i == num_transient_) { + transient_make_room_(num_transient_ + 1); + transient_[i].widget = wi; + num_transient_++; + } + transient_[i].cell = new_cell; + if (remove_old_cell) { + remove_cell(old_cell->row(), old_cell->col()); + } + return new_cell; +} + +/** + Make room for at least n transient widgets in the array. + \param[in] n minimum number of entries + */ +void Fl_Grid_Proxy::transient_make_room_(int n) { + if (n > cap_transient_) { + cap_transient_ = n + 10; + transient_ = (Cell_Widget_Pair*)::realloc(transient_, cap_transient_ * sizeof(Cell_Widget_Pair)); + } +} + +/** + Remove a widget form the list and deallocate the transient cell. + \param[in] w remove the transient cell for this widget + */ +void Fl_Grid_Proxy::transient_remove_(Fl_Widget *w) { + for (int i=0; i<num_transient_; i++) { + if (transient_[i].widget==w) { + if (transient_[i].cell) { + ::free(transient_[i].cell); + ::memmove(transient_+i, transient_+i+1, sizeof(Cell_Widget_Pair)*(num_transient_-i-1)); + num_transient_--; + return; + } + } + } +} + +/** + Find a cell in the grid or in the transient cell list. + \param[in] widget must be a child of the grid. + \return the cell, the transient cell, or NULL if neither was found. + */ +Fl_Grid_Proxy::Cell *Fl_Grid_Proxy::any_cell(Fl_Widget *widget) const { + Cell *c = cell(widget); + if (c) return c; + for (int i=0; i<num_transient_; i++) { + if (transient_[i].widget == widget) + return transient_[i].cell; + } + return NULL; +} + +/** + Forwarding the call. + \param[in] wi generate a cell for this widget + \param[in] row, col, align cell parameters + */ +Fl_Grid::Cell *Fl_Grid_Proxy::widget(Fl_Widget *wi, int row, int col, Fl_Grid_Align align) { + return widget(wi, row, col, 1, 1, align); +} + +/** + Just like the Fl_Grid original, but removes potential transient cell. + \param[in] wi generate a cell for this widget + \param[in] row, col, rowspan, colspan, align cell parameters + */ +Fl_Grid::Cell *Fl_Grid_Proxy::widget(Fl_Widget *wi, int row, int col, int rowspan, int colspan, Fl_Grid_Align align) { + transient_remove_(wi); + return Fl_Grid::widget(wi, row, col, rowspan, colspan, align); +} + + + // ---- Fl_Grid_Type --------------------------------------------------- MARK: - const char grid_type_name[] = "Fl_Grid"; @@ -432,29 +634,8 @@ Fl_Grid *Fl_Grid_Type::selected() { return NULL; } -extern Fluid_Coord_Input *widget_grid_row_input, *widget_grid_col_input, - *widget_grid_rowspan_input, *widget_grid_colspan_input; -extern Fl_Group *widget_tab_grid_child; - -static void move_cell(Fl_Grid *grid, Fl_Widget *child, int to_row, int to_col) { - short rowspan = 1, colspan = 1; - Fl_Grid_Align align = FL_GRID_FILL; - int w = 20, h = 20; - const Fl_Grid::Cell *old_cell = grid->cell(child); - if (old_cell) { - rowspan = old_cell->rowspan(); - colspan = old_cell->colspan(); - align = old_cell->align(); - old_cell->minimum_size(&w, &h); - } - if ((to_row<0) || (to_row+rowspan>grid->rows())) return; - if ((to_col<0) || (to_col+colspan>grid->cols())) return; - Fl_Grid::Cell *new_cell = grid->widget(child, to_row, to_col, rowspan, colspan, align); - if (new_cell) new_cell->minimum_size(w, h); -} - void Fl_Grid_Type::insert_child_at(Fl_Widget *child, int x, int y) { - Fl_Grid *grid = (Fl_Grid*)o; + Fl_Grid_Proxy *grid = (Fl_Grid_Proxy*)o; int row = -1, col = -1, ml, mt, grg, gcg; grid->margin(&ml, &mt, NULL, NULL); grid->gap(&grg, &gcg); @@ -475,22 +656,24 @@ void Fl_Grid_Type::insert_child_at(Fl_Widget *child, int x, int y) { x0 += gap; } - move_cell(grid, child, row, col); + grid->move_cell(child, row, col); } -/** Insert a child window into the first new cell we can find . +/** Insert a child widget into the first new cell we can find . There are many other possible strategies. How about inserting to the right of the last added child. Also, what happens if the grid is full? Should we add a new row at the bottom? + + /param[in] child */ void Fl_Grid_Type::insert_child(Fl_Widget *child) { - Fl_Grid *grid = (Fl_Grid*)o; + Fl_Grid_Proxy *grid = (Fl_Grid_Proxy*)o; if (grid->cell(child)) return; for (int r=0; r<grid->rows(); r++) { for (int c=0; c<grid->cols(); c++) { if (!grid->cell(r, c)) { - move_cell(grid, child, r, c); + grid->move_cell(child, r, c); return; } } @@ -506,17 +689,17 @@ void Fl_Grid_Type::insert_child(Fl_Widget *child) { \param[in] key code of the last keypress when handling a FL_KEYBOARD event. */ void Fl_Grid_Type::keyboard_move_child(Fl_Widget_Type *child, int key) { - Fl_Grid *grid = ((Fl_Grid*)o); - Fl_Grid::Cell *cell = grid->cell(child->o); + Fl_Grid_Proxy *grid = ((Fl_Grid_Proxy*)o); + Fl_Grid::Cell *cell = grid->any_cell(child->o); if (!cell) return; if (key == FL_Right) { - move_cell(grid, child->o, cell->row(), cell->col()+1); + grid->move_cell(child->o, cell->row(), cell->col()+1, 2); } else if (key == FL_Left) { - move_cell(grid, child->o, cell->row(), cell->col()-1); + grid->move_cell(child->o, cell->row(), cell->col()-1, 2); } else if (key == FL_Up) { - move_cell(grid, child->o, cell->row()-1, cell->col()); + grid->move_cell(child->o, cell->row()-1, cell->col(), 2); } else if (key == FL_Down) { - move_cell(grid, child->o, cell->row()+1, cell->col()); + grid->move_cell(child->o, cell->row()+1, cell->col(), 2); } } @@ -544,6 +727,12 @@ void Fl_Grid_Type::layout_widget() { // TODO: ways to resize rows and columns, add and delete them in the project window, pulldown menu? // TODO: alignment can be FL_GRID_LEFT|FL_GRID_VERTICAL? // TODO: we must set undo checkpoints in all callbacks! + +extern Fluid_Coord_Input *widget_grid_row_input, *widget_grid_col_input, +*widget_grid_rowspan_input, *widget_grid_colspan_input; +extern Fl_Group *widget_tab_grid_child; + + void grid_child_cb(Fluid_Coord_Input* i, void* v, int what) { static Fl_Widget *prev_widget = NULL; if ( !current_widget @@ -553,7 +742,7 @@ void grid_child_cb(Fluid_Coord_Input* i, void* v, int what) { return; } Fl_Widget *child = ((Fl_Widget_Type*)current_widget)->o; - Fl_Grid *g = ((Fl_Grid*)((Fl_Widget_Type*)current_widget->parent)->o); + Fl_Grid_Proxy *g = ((Fl_Grid_Proxy*)((Fl_Widget_Type*)current_widget->parent)->o); Fl_Grid::Cell *cell = g->cell(child); bool freeze_row_col = (!cell && prev_widget==child && ((what&0x00ff)==8 || (what&0x00ff)==9)); if (v == LOAD) { @@ -593,11 +782,11 @@ void grid_child_cb(Fluid_Coord_Input* i, void* v, int what) { } if (old_v != v) { switch (what & 0x00ff) { - case 8: if (v>=0 && v2>=0) move_cell(g, current_widget->o, v, v2); - if (freeze_row_col) i->value(v); + case 8: if (v>=0 && v2>=0) g->move_cell(current_widget->o, v, v2); + if (freeze_row_col) i->value(v); break; - case 9: if (v>=0 && v2>=0) move_cell(g, current_widget->o, v2, v); - if (freeze_row_col) i->value(v); + case 9: if (v>=0 && v2>=0) g->move_cell(current_widget->o, v2, v); + if (freeze_row_col) i->value(v); break; case 10: if (cell && cell->row()+v<=g->rows()) cell->rowspan(v); break; diff --git a/fluid/Fl_Grid_Type.h b/fluid/Fl_Grid_Type.h index 5ab87b8a5..9780ace76 100644 --- a/fluid/Fl_Grid_Type.h +++ b/fluid/Fl_Grid_Type.h @@ -25,11 +25,24 @@ extern const char grid_type_name[]; class Fl_Grid_Proxy : public Fl_Grid { +protected: + typedef struct { Fl_Widget *widget; Cell *cell; } Cell_Widget_Pair; + Cell_Widget_Pair *transient_; + int num_transient_; + int cap_transient_; + void transient_make_room_(int n); + void transient_remove_(Fl_Widget *w); public: - Fl_Grid_Proxy(int X,int Y,int W,int H) : Fl_Grid(X,Y,W,H) {} + Fl_Grid_Proxy(int X,int Y,int W,int H); + ~Fl_Grid_Proxy(); void resize(int,int,int,int) FL_OVERRIDE; void draw() FL_OVERRIDE; void draw_overlay(); + void move_cell(Fl_Widget *child, int to_row, int to_col, int how = 0); + Cell* any_cell(Fl_Widget *widget) const; + Cell* transient_widget(Fl_Widget *wi, int row, int col, int row_span, int col_span, Fl_Grid_Align align = FL_GRID_FILL); + Cell* widget(Fl_Widget *wi, int row, int col, Fl_Grid_Align align = FL_GRID_FILL); + Cell* widget(Fl_Widget *wi, int row, int col, int rowspan, int colspan, Fl_Grid_Align align = FL_GRID_FILL); }; class Fl_Grid_Type : public Fl_Group_Type diff --git a/fluid/alignment_panel.fl b/fluid/alignment_panel.fl index 464970348..66335f095 100644 --- a/fluid/alignment_panel.fl +++ b/fluid/alignment_panel.fl @@ -282,7 +282,7 @@ Examples: } Fl_Check_Button ghosted_outline_button { label {Show Ghosted Group Outlines} - callback toggle_ghosted_outline_cb selected + callback toggle_ghosted_outline_cb tooltip {groups with no box type or flat boxtypes without contrast will be rendered with a dim outline in the editing window only} xywh {120 340 200 20} down_box DOWN_BOX labelsize 11 code0 {o->value(show_ghosted_outline);} } @@ -1565,6 +1565,36 @@ settings_window->hide();} } } } + Fl_Window {} {open + xywh {646 417 480 320} type Double visible + } { + Fl_Grid {} {open + xywh {25 25 240 160} + dimensions {3 3} + } { + Fl_Button {} { + label Button + xywh {25 25 80 66} + parent_properties { + location {0 0} + } + } + Fl_Light_Button {} { + label Button + xywh {105 25 80 66} + parent_properties { + location {0 1} + } + } + Fl_Button {} { + label Button selected + xywh {185 25 80 66} + parent_properties { + location {0 2} + } + } + } + } code {w_settings_tabs->do_callback(w_settings_tabs, LOAD);} {} } diff --git a/fluid/documentation/src-dev/page_introduction.dox b/fluid/documentation/src-dev/page_introduction.dox index 1db902d44..5684eeeda 100644 --- a/fluid/documentation/src-dev/page_introduction.dox +++ b/fluid/documentation/src-dev/page_introduction.dox @@ -64,4 +64,23 @@ ... + ## Wish List ## + + I call this a wish list because I write my wishes down as I go. There is no + verification yet, or alternative ideas. + + - group source files into subdirectories + - all panels should have their own folder + - all custom widgets + - all file and stream operations + - all utilities and tools + - move to C++17 and use std::string, vector, map, ... + - Fl_Type::write#() could go into a single write function with an enum to + describe what needs to be written to make thing logical and expandable + - better error handling in all file classes + - separate FLUID generated files form the core and build a shell-only version + - individual event handling for types + - individual overlay drawing for types + - plug-ins for new types + */ |
