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