cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

scrollbar.c (16303B)


      1/*
      2 * Licensed to the Apache Software Foundation (ASF) under one
      3 * or more contributor license agreements.  See the NOTICE file
      4 * distributed with this work for additional information
      5 * regarding copyright ownership.  The ASF licenses this file
      6 * to you under the Apache License, Version 2.0 (the
      7 * "License"); you may not use this file except in compliance
      8 * with the License.  You may obtain a copy of the License at
      9 *
     10 *   http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing,
     13 * software distributed under the License is distributed on an
     14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15 * KIND, either express or implied.  See the License for the
     16 * specific language governing permissions and limitations
     17 * under the License.
     18 */
     19
     20#include "terminal/scrollbar.h"
     21
     22#include <guacamole/client.h>
     23#include <guacamole/layer.h>
     24#include <guacamole/mem.h>
     25#include <guacamole/socket.h>
     26#include <guacamole/protocol.h>
     27#include <guacamole/user.h>
     28
     29#include <stdlib.h>
     30
     31guac_terminal_scrollbar* guac_terminal_scrollbar_alloc(guac_client* client,
     32        const guac_layer* parent, int parent_width, int parent_height, int visible_area) {
     33
     34    /* Allocate scrollbar */
     35    guac_terminal_scrollbar* scrollbar =
     36        guac_mem_alloc(sizeof(guac_terminal_scrollbar));
     37
     38    /* Associate client */
     39    scrollbar->client = client;
     40
     41    /* Init default min/max and value */
     42    scrollbar->min   = 0;
     43    scrollbar->max   = 0;
     44    scrollbar->value = 0;
     45
     46    /* Init parent data */
     47    scrollbar->parent        = parent;
     48    scrollbar->parent_width  = 0;
     49    scrollbar->parent_height = 0;
     50    scrollbar->visible_area  = 0;
     51
     52    /* Init handle render state */
     53    scrollbar->render_state.handle_x      = 0;
     54    scrollbar->render_state.handle_y      = 0;
     55    scrollbar->render_state.handle_width  = 0;
     56    scrollbar->render_state.handle_height = 0;
     57
     58    /* Init container render state */
     59    scrollbar->render_state.container_x      = 0;
     60    scrollbar->render_state.container_y      = 0;
     61    scrollbar->render_state.container_width  = 0;
     62    scrollbar->render_state.container_height = 0;
     63
     64    /* Allocate and init layers */
     65    scrollbar->container = guac_client_alloc_layer(client);
     66    scrollbar->handle    = guac_client_alloc_layer(client);
     67
     68    /* Init mouse event state tracking */
     69    scrollbar->dragging_handle = 0;
     70
     71    /* Reposition and resize to fit parent */
     72    guac_terminal_scrollbar_parent_resized(scrollbar,
     73            parent_width, parent_height, visible_area);
     74
     75    return scrollbar;
     76
     77}
     78
     79void guac_terminal_scrollbar_free(guac_terminal_scrollbar* scrollbar) {
     80
     81    /* Free layers */
     82    guac_client_free_layer(scrollbar->client, scrollbar->handle);
     83    guac_client_free_layer(scrollbar->client, scrollbar->container);
     84
     85    /* Free scrollbar */
     86    guac_mem_free(scrollbar);
     87
     88}
     89
     90/**
     91 * Moves the main scrollbar layer to the position indicated within the given
     92 * scrollbar render state, sending any necessary Guacamole instructions over
     93 * the given socket.
     94 *
     95 * @param scrollbar
     96 *     The scrollbar to reposition.
     97 *
     98 * @param state
     99 *     The guac_terminal_scrollbar_render_state describing the new scrollbar
    100 *     position.
    101 *
    102 * @param socket
    103 *     The guac_socket over which any instructions necessary to perform the
    104 *     render operation should be sent.
    105 */
    106static void guac_terminal_scrollbar_move_container(
    107        guac_terminal_scrollbar* scrollbar,
    108        guac_terminal_scrollbar_render_state* state,
    109        guac_socket* socket) {
    110
    111    /* Send scrollbar position */
    112    guac_protocol_send_move(socket,
    113            scrollbar->container, scrollbar->parent,
    114            state->container_x,
    115            state->container_y,
    116            0);
    117
    118}
    119
    120/**
    121 * Resizes and redraws the main scrollbar layer according to the given
    122 * scrollbar render state, sending any necessary Guacamole instructions over
    123 * the given socket.
    124 *
    125 * @param scrollbar
    126 *     The scrollbar to resize and redraw.
    127 *
    128 * @param state
    129 *     The guac_terminal_scrollbar_render_state describing the new scrollbar
    130 *     size and appearance.
    131 *
    132 * @param socket
    133 *     The guac_socket over which any instructions necessary to perform the
    134 *     render operation should be sent.
    135 */
    136static void guac_terminal_scrollbar_draw_container(
    137        guac_terminal_scrollbar* scrollbar,
    138        guac_terminal_scrollbar_render_state* state,
    139        guac_socket* socket) {
    140
    141    /* Set container size */
    142    guac_protocol_send_size(socket, scrollbar->container,
    143            state->container_width,
    144            state->container_height);
    145
    146    /* Fill container with solid color */
    147    guac_protocol_send_rect(socket, scrollbar->container, 0, 0,
    148            state->container_width,
    149            state->container_height);
    150
    151    guac_protocol_send_cfill(socket, GUAC_COMP_SRC, scrollbar->container,
    152            0x80, 0x80, 0x80, 0x40);
    153
    154}
    155
    156/**
    157 * Moves the handle layer of the scrollbar to the position indicated within the
    158 * given scrollbar render state, sending any necessary Guacamole instructions
    159 * over the given socket. The handle is the portion of the scrollbar that
    160 * indicates the current scroll value and which the user can click and drag to
    161 * change the value.
    162 *
    163 * @param scrollbar
    164 *     The scrollbar associated with the handle being repositioned.
    165 *
    166 * @param state
    167 *     The guac_terminal_scrollbar_render_state describing the new scrollbar
    168 *     handle position.
    169 *
    170 * @param socket
    171 *     The guac_socket over which any instructions necessary to perform the
    172 *     render operation should be sent.
    173 */
    174static void guac_terminal_scrollbar_move_handle(
    175        guac_terminal_scrollbar* scrollbar,
    176        guac_terminal_scrollbar_render_state* state,
    177        guac_socket* socket) {
    178
    179    /* Send handle position */
    180    guac_protocol_send_move(socket,
    181            scrollbar->handle, scrollbar->container,
    182            state->handle_x,
    183            state->handle_y,
    184            0);
    185
    186}
    187
    188/**
    189 * Resizes and redraws the handle layer of the scrollbar according to the given
    190 * scrollbar render state, sending any necessary Guacamole instructions over
    191 * the given socket. The handle is the portion of the scrollbar that indicates
    192 * the current scroll value and which the user can click and drag to change the
    193 * value.
    194 *
    195 * @param scrollbar
    196 *     The scrollbar associated with the handle being resized and redrawn.
    197 *
    198 * @param state
    199 *     The guac_terminal_scrollbar_render_state describing the new scrollbar
    200 *     handle size and appearance.
    201 *
    202 * @param socket
    203 *     The guac_socket over which any instructions necessary to perform the
    204 *     render operation should be sent.
    205 */
    206static void guac_terminal_scrollbar_draw_handle(
    207        guac_terminal_scrollbar* scrollbar,
    208        guac_terminal_scrollbar_render_state* state,
    209        guac_socket* socket) {
    210
    211    /* Set handle size */
    212    guac_protocol_send_size(socket, scrollbar->handle,
    213            state->handle_width,
    214            state->handle_height);
    215
    216    /* Fill handle with solid color */
    217    guac_protocol_send_rect(socket, scrollbar->handle, 0, 0,
    218            state->handle_width,
    219            state->handle_height);
    220
    221    guac_protocol_send_cfill(socket, GUAC_COMP_SRC, scrollbar->handle,
    222            0xA0, 0xA0, 0xA0, 0x8F);
    223
    224}
    225
    226/**
    227 * Calculates the state of the scroll bar, given its minimum, maximum, current
    228 * values, and the state of any dragging operation. The resulting render state
    229 * will not be reflected graphically unless the scrollbar is flushed, and any
    230 * resulting value will not be assigned to the scrollbar unless explicitly set
    231 * with guac_terminal_scrollbar_set_value().
    232 *
    233 * @param scrollbar
    234 *     The scrollbar whose state should be calculated.
    235 *
    236 * @param render_state
    237 *     A pointer to an existing guac_terminal_scrollbar_render_state that will
    238 *     be populated with the calculated result.
    239 *
    240 * @param value
    241 *     A pointer to an existing int that will be populated with the updated
    242 *     scrollbar value.
    243 */
    244static void calculate_state(guac_terminal_scrollbar* scrollbar,
    245        guac_terminal_scrollbar_render_state* render_state,
    246        int* value) {
    247
    248    /* Use unchanged current value by default */
    249    *value = scrollbar->value;
    250
    251    /* Calculate container dimensions */
    252    render_state->container_width  = GUAC_TERMINAL_SCROLLBAR_WIDTH;
    253    render_state->container_height = scrollbar->parent_height;
    254
    255    /* Calculate container position */
    256    render_state->container_x = scrollbar->parent_width
    257                              - render_state->container_width;
    258
    259    render_state->container_y = 0;
    260
    261    /* Calculate handle dimensions */
    262    render_state->handle_width  = render_state->container_width
    263                                - GUAC_TERMINAL_SCROLLBAR_PADDING*2;
    264
    265    /* Handle can be no bigger than the scrollbar itself */
    266    int max_handle_height = render_state->container_height
    267                          - GUAC_TERMINAL_SCROLLBAR_PADDING*2;
    268
    269    /* Calculate legal delta between scroll values */
    270    int scroll_delta;
    271    if (scrollbar->max > scrollbar->min)
    272        scroll_delta = scrollbar->max - scrollbar->min;
    273    else
    274        scroll_delta = 0;
    275
    276    /* Scale handle relative to visible area vs. scrolling region size */
    277    int proportional_height = max_handle_height
    278                            * scrollbar->visible_area
    279                            / (scroll_delta + scrollbar->visible_area);
    280
    281    /* Ensure handle is no smaller than minimum height */
    282    if (proportional_height > GUAC_TERMINAL_SCROLLBAR_MIN_HEIGHT)
    283        render_state->handle_height = proportional_height;
    284    else
    285        render_state->handle_height = GUAC_TERMINAL_SCROLLBAR_MIN_HEIGHT;
    286
    287    /* Ensure handle is no larger than maximum height */
    288    if (render_state->handle_height > max_handle_height)
    289        render_state->handle_height = max_handle_height;
    290
    291    /* Calculate handle X position */
    292    render_state->handle_x = GUAC_TERMINAL_SCROLLBAR_PADDING;
    293
    294    /* Calculate handle Y range */
    295    int min_handle_y = GUAC_TERMINAL_SCROLLBAR_PADDING;
    296    int max_handle_y = min_handle_y + max_handle_height
    297                     - render_state->handle_height;
    298
    299    /* Position handle relative to mouse if being dragged */
    300    if (scrollbar->dragging_handle) {
    301
    302        int dragged_handle_y = scrollbar->drag_current_y
    303                             - scrollbar->drag_offset_y;
    304
    305        /* Keep handle within bounds */
    306        if (dragged_handle_y < min_handle_y)
    307            dragged_handle_y = min_handle_y;
    308        else if (dragged_handle_y > max_handle_y)
    309            dragged_handle_y = max_handle_y;
    310
    311        render_state->handle_y = dragged_handle_y;
    312
    313        /* Calculate scrollbar value */
    314        if (max_handle_y > min_handle_y) {
    315            *value = scrollbar->min
    316                   + (dragged_handle_y - min_handle_y)
    317                      * scroll_delta
    318                      / (max_handle_y - min_handle_y);
    319        }
    320
    321    }
    322
    323    /* Handle Y position is relative to current scroll value */
    324    else if (scroll_delta > 0)
    325        render_state->handle_y = min_handle_y
    326                               + (max_handle_y - min_handle_y)
    327                                  * (scrollbar->value - scrollbar->min)
    328                                  / scroll_delta;
    329
    330    /* ... unless there is only one possible scroll value */
    331    else
    332        render_state->handle_y = GUAC_TERMINAL_SCROLLBAR_PADDING;
    333
    334}
    335
    336void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar,
    337        guac_client* client, guac_socket* socket) {
    338
    339    /* Get old state */
    340    guac_terminal_scrollbar_render_state* state = &scrollbar->render_state;
    341
    342    /* Send scrollbar container */
    343    guac_terminal_scrollbar_draw_container(scrollbar, state, socket);
    344    guac_terminal_scrollbar_move_container(scrollbar, state, socket);
    345
    346    /* Send handle */
    347    guac_terminal_scrollbar_draw_handle(scrollbar, state, socket);
    348    guac_terminal_scrollbar_move_handle(scrollbar, state, socket);
    349
    350}
    351
    352void guac_terminal_scrollbar_flush(guac_terminal_scrollbar* scrollbar) {
    353
    354    guac_socket* socket = scrollbar->client->socket;
    355
    356    /* Get old state */
    357    int old_value = scrollbar->value;
    358    guac_terminal_scrollbar_render_state* old_state = &scrollbar->render_state;
    359
    360    /* Calculate new state */
    361    int new_value;
    362    guac_terminal_scrollbar_render_state new_state;
    363    calculate_state(scrollbar, &new_state, &new_value);
    364
    365    /* Notify of scroll if value is changing */
    366    if (new_value != old_value && scrollbar->scroll_handler)
    367        scrollbar->scroll_handler(scrollbar, new_value);
    368
    369    /* Reposition container if moved */
    370    if (old_state->container_x != new_state.container_x
    371     || old_state->container_y != new_state.container_y) {
    372        guac_terminal_scrollbar_move_container(scrollbar, &new_state, socket);
    373    }
    374
    375    /* Resize and redraw container if size changed */
    376    if (old_state->container_width  != new_state.container_width
    377     || old_state->container_height != new_state.container_height) {
    378        guac_terminal_scrollbar_draw_container(scrollbar, &new_state, socket);
    379    }
    380
    381    /* Reposition handle if moved */
    382    if (old_state->handle_x != new_state.handle_x
    383     || old_state->handle_y != new_state.handle_y) {
    384        guac_terminal_scrollbar_move_handle(scrollbar, &new_state, socket);
    385    }
    386
    387    /* Resize and redraw handle if size changed */
    388    if (old_state->handle_width  != new_state.handle_width
    389     || old_state->handle_height != new_state.handle_height) {
    390        guac_terminal_scrollbar_draw_handle(scrollbar, &new_state, socket);
    391    }
    392
    393    /* Store current render state */
    394    scrollbar->render_state = new_state;
    395
    396}
    397
    398void guac_terminal_scrollbar_set_bounds(guac_terminal_scrollbar* scrollbar,
    399        int min, int max) {
    400
    401    /* Fit value within bounds */
    402    if (scrollbar->value > max)
    403        scrollbar->value = max;
    404    else if (scrollbar->value < min)
    405        scrollbar->value = min;
    406
    407    /* Update bounds */
    408    scrollbar->min = min;
    409    scrollbar->max = max;
    410
    411}
    412
    413void guac_terminal_scrollbar_set_value(guac_terminal_scrollbar* scrollbar,
    414        int value) {
    415
    416    /* Fit value within bounds */
    417    if (value > scrollbar->max)
    418        value = scrollbar->max;
    419    else if (value < scrollbar->min)
    420        value = scrollbar->min;
    421
    422    /* Update value */
    423    scrollbar->value = value;
    424
    425}
    426
    427void guac_terminal_scrollbar_parent_resized(guac_terminal_scrollbar* scrollbar,
    428        int parent_width, int parent_height, int visible_area) {
    429
    430    /* Assign new dimensions */
    431    scrollbar->parent_width  = parent_width;
    432    scrollbar->parent_height = parent_height;
    433    scrollbar->visible_area  = visible_area;
    434
    435}
    436
    437int guac_terminal_scrollbar_handle_mouse(guac_terminal_scrollbar* scrollbar,
    438        int x, int y, int mask) {
    439
    440    /* Get container rectangle bounds */
    441    int parent_left   = scrollbar->render_state.container_x;
    442    int parent_top    = scrollbar->render_state.container_y;
    443    int parent_right  = parent_left + scrollbar->render_state.container_width;
    444    int parent_bottom = parent_top  + scrollbar->render_state.container_height;
    445
    446    /* Calculate handle rectangle bounds */
    447    int handle_left   = parent_left + scrollbar->render_state.handle_x;
    448    int handle_top    = parent_top  + scrollbar->render_state.handle_y;
    449    int handle_right  = handle_left + scrollbar->render_state.handle_width;
    450    int handle_bottom = handle_top  + scrollbar->render_state.handle_height;
    451
    452    /* Handle click on handle */
    453    if (scrollbar->dragging_handle) {
    454
    455        /* Update drag while mouse button is held */
    456        if (mask & GUAC_CLIENT_MOUSE_LEFT)
    457            scrollbar->drag_current_y = y;
    458
    459        /* Stop drag if mouse button is released */
    460        else
    461            scrollbar->dragging_handle = 0;
    462
    463        /* Mouse event was handled by scrollbar */
    464        return 1;
    465
    466    }
    467    else if (mask == GUAC_CLIENT_MOUSE_LEFT
    468            && x >= handle_left && x < handle_right
    469            && y >= handle_top  && y < handle_bottom) {
    470
    471        /* Start drag */
    472        scrollbar->dragging_handle = 1;
    473        scrollbar->drag_offset_y = y - handle_top;
    474        scrollbar->drag_current_y = y;
    475
    476        /* Mouse event was handled by scrollbar */
    477        return 1;
    478
    479    }
    480
    481    /* Eat any events that occur within the scrollbar */
    482    return x >= parent_left && x < parent_right
    483        && y >= parent_top  && y < parent_bottom;
    484
    485}
    486