/* * Copyright (c) 2012 Petr Koupy * Copyright (c) 2013 Martin Decky * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup gui * @{ */ /** * @file */ #include #include #include #include #include "window.h" #include "grid.h" typedef struct { sysarg_t min; sysarg_t max; sysarg_t val; } constraints_t; static void paint_internal(widget_t *widget) { grid_t *grid = (grid_t *) widget; surface_t *surface = window_claim(grid->widget.window); if (!surface) { window_yield(grid->widget.window); return; } // FIXME: Replace with (accelerated) rectangle fill for (sysarg_t y = widget->vpos; y < widget->vpos + widget->height; y++) { for (sysarg_t x = widget->hpos; x < widget->hpos + widget->width; x++) surface_put_pixel(surface, x, y, grid->background); } window_yield(grid->widget.window); } static grid_cell_t *grid_cell_at(grid_t *grid, size_t col, size_t row) { if ((col < grid->cols) && (row < grid->rows)) return grid->layout + (row * grid->cols + col); return NULL; } static grid_cell_t *grid_coords_at(grid_t *grid, sysarg_t hpos, sysarg_t vpos) { for (size_t c = 0; c < grid->cols; c++) { for (size_t r = 0; r < grid->rows; r++) { grid_cell_t *cell = grid_cell_at(grid, c, r); if (cell) { widget_t *widget = cell->widget; if ((widget) && (hpos >= widget->hpos) && (vpos >= widget->vpos) && (hpos < widget->hpos + widget->width) && (vpos < widget->vpos + widget->height)) return cell; } } } return NULL; } void deinit_grid(grid_t *grid) { widget_deinit(&grid->widget); free(grid->layout); } static void grid_destroy(widget_t *widget) { grid_t *grid = (grid_t *) widget; deinit_grid(grid); free(grid); } static void grid_reconfigure(widget_t *widget) { /* No-op */ } static void adjust_constraints(constraints_t *cons, size_t run, sysarg_t dim_min, sysarg_t dim_max) { assert(run > 0); sysarg_t dim_min_part = dim_min / run; sysarg_t dim_min_rem = dim_min % run; sysarg_t dim_max_part = dim_max / run; sysarg_t dim_max_rem = dim_max % run; for (size_t i = 0; i < run; i++) { sysarg_t dim_min_cur = dim_min_part; sysarg_t dim_max_cur = dim_max_part; if (i == run - 1) { dim_min_cur += dim_min_rem; dim_max_cur += dim_max_rem; } /* * We want the strongest constraint * for the minimum. */ if (cons[i].min < dim_min_cur) cons[i].min = dim_min_cur; /* * The comparison is correct, we want * the weakest constraint for the * maximum. */ if (cons[i].max < dim_max_cur) cons[i].max = dim_max_cur; } } static void solve_constraints(constraints_t *cons, size_t run, sysarg_t sum) { /* Initial solution */ sysarg_t cur_sum = 0; for (size_t i = 0; i < run; i++) { cons[i].val = cons[i].min; cur_sum += cons[i].val; } /* Iterative improvement */ while (cur_sum < sum) { sysarg_t delta = (sum - cur_sum) / run; if (delta == 0) break; cur_sum = 0; for (size_t i = 0; i < run; i++) { if (cons[i].val + delta < cons[i].max) cons[i].val += delta; cur_sum += cons[i].val; } } } static void grid_rearrange(widget_t *widget, sysarg_t hpos, sysarg_t vpos, sysarg_t width, sysarg_t height) { grid_t *grid = (grid_t *) widget; widget_modify(widget, hpos, vpos, width, height); paint_internal(widget); /* Compute column widths */ constraints_t *widths = (constraints_t *) calloc(grid->cols, sizeof(constraints_t)); if (widths) { /* Constrain widths */ for (size_t c = 0; c < grid->cols; c++) { widths[c].min = 0; for (size_t r = 0; r < grid->rows; r++) { grid_cell_t *cell = grid_cell_at(grid, c, r); if (!cell) continue; widget_t *widget = cell->widget; if (widget) adjust_constraints(&widths[c], cell->cols, widget->width_min, widget->width_max); } } solve_constraints(widths, grid->cols, width); } /* Compute row heights */ constraints_t *heights = (constraints_t *) calloc(grid->rows, sizeof(constraints_t)); if (heights) { /* Constrain heights */ for (size_t r = 0; r < grid->rows; r++) { heights[r].min = 0; for (size_t c = 0; c < grid->cols; c++) { grid_cell_t *cell = grid_cell_at(grid, c, r); if (!cell) continue; widget_t *widget = cell->widget; if (widget) { adjust_constraints(&heights[r], cell->rows, widget->height_min, widget->height_max); } } } solve_constraints(heights, grid->rows, height); } /* Rearrange widgets */ if ((widths) && (heights)) { sysarg_t cur_vpos = vpos; for (size_t r = 0; r < grid->rows; r++) { sysarg_t cur_hpos = hpos; for (size_t c = 0; c < grid->cols; c++) { grid_cell_t *cell = grid_cell_at(grid, c, r); if (!cell) continue; widget_t *widget = cell->widget; if (widget) { sysarg_t cur_width = 0; sysarg_t cur_height = 0; for (size_t cd = 0; cd < cell->cols; cd++) cur_width += widths[c + cd].val; for (size_t rd = 0; rd < cell->rows; rd++) cur_height += heights[r + rd].val; if ((cur_width > 0) && (cur_height > 0)) { sysarg_t wwidth = cur_width; sysarg_t wheight = cur_height; /* * Make sure the widget is respects its * maximal constrains. */ if ((widget->width_max > 0) && (wwidth > widget->width_max)) wwidth = widget->width_max; if ((widget->height_max > 0) && (wheight > widget->height_max)) wheight = widget->height_max; widget->rearrange(widget, cur_hpos, cur_vpos, wwidth, wheight); } } cur_hpos += widths[c].val; } cur_vpos += heights[r].val; } } if (widths) free(widths); if (heights) free(heights); } static void grid_repaint(widget_t *widget) { paint_internal(widget); list_foreach(widget->children, link, widget_t, child) { child->repaint(child); } window_damage(widget->window); } static void grid_handle_keyboard_event(widget_t *widget, kbd_event_t event) { /* No-op */ } static void grid_handle_position_event(widget_t *widget, pos_event_t event) { grid_t *grid = (grid_t *) widget; grid_cell_t *cell = grid_coords_at(grid, event.hpos, event.vpos); if ((cell) && (cell->widget)) cell->widget->handle_position_event(cell->widget, event); } static bool grid_add(struct grid *grid, widget_t *widget, size_t col, size_t row, size_t cols, size_t rows) { if ((cols == 0) || (rows == 0) || (col + cols > grid->cols) || (row + rows > grid->rows)) return false; grid_cell_t *cell = grid_cell_at(grid, col, row); if (!cell) return false; /* * Check whether the cell is not occupied by an * extension of a different cell. */ if ((!cell->widget) && (cell->cols > 0) && (cell->rows > 0)) return false; widget->parent = (widget_t *) grid; list_append(&widget->link, &grid->widget.children); widget->window = grid->widget.window; /* Mark cells in layout */ for (size_t r = row; r < row + rows; r++) { for (size_t c = col; c < col + cols; c++) { if ((r == row) && (c == col)) { cell->widget = widget; cell->cols = cols; cell->rows = rows; } else { grid_cell_t *extension = grid_cell_at(grid, c, r); if (extension) { extension->widget = NULL; extension->cols = 1; extension->rows = 1; } } } } return true; } bool init_grid(grid_t *grid, widget_t *parent, const void *data, size_t cols, size_t rows, pixel_t background) { if ((cols == 0) || (rows == 0)) return false; grid->layout = (grid_cell_t *) calloc(cols * rows, sizeof(grid_cell_t)); if (!grid->layout) return false; memset(grid->layout, 0, cols * rows * sizeof(grid_cell_t)); widget_init(&grid->widget, parent, data); grid->widget.destroy = grid_destroy; grid->widget.reconfigure = grid_reconfigure; grid->widget.rearrange = grid_rearrange; grid->widget.repaint = grid_repaint; grid->widget.handle_keyboard_event = grid_handle_keyboard_event; grid->widget.handle_position_event = grid_handle_position_event; grid->add = grid_add; grid->background = background; grid->cols = cols; grid->rows = rows; return true; } grid_t *create_grid(widget_t *parent, const void *data, size_t cols, size_t rows, pixel_t background) { grid_t *grid = (grid_t *) malloc(sizeof(grid_t)); if (!grid) return NULL; if (init_grid(grid, parent, data, cols, rows, background)) return grid; free(grid); return NULL; } /** @} */