guacamole-spice-protocol/src/terminal/scrollbar.c

485 lines
16 KiB
C

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "terminal/scrollbar.h"
#include <guacamole/client.h>
#include <guacamole/layer.h>
#include <guacamole/socket.h>
#include <guacamole/protocol.h>
#include <stdlib.h>
guac_terminal_scrollbar* guac_terminal_scrollbar_alloc(guac_client* client,
const guac_layer* parent, int parent_width, int parent_height, int visible_area) {
/* Allocate scrollbar */
guac_terminal_scrollbar* scrollbar =
malloc(sizeof(guac_terminal_scrollbar));
/* Associate client */
scrollbar->client = client;
/* Init default min/max and value */
scrollbar->min = 0;
scrollbar->max = 0;
scrollbar->value = 0;
/* Init parent data */
scrollbar->parent = parent;
scrollbar->parent_width = 0;
scrollbar->parent_height = 0;
scrollbar->visible_area = 0;
/* Init handle render state */
scrollbar->render_state.handle_x = 0;
scrollbar->render_state.handle_y = 0;
scrollbar->render_state.handle_width = 0;
scrollbar->render_state.handle_height = 0;
/* Init container render state */
scrollbar->render_state.container_x = 0;
scrollbar->render_state.container_y = 0;
scrollbar->render_state.container_width = 0;
scrollbar->render_state.container_height = 0;
/* Allocate and init layers */
scrollbar->container = guac_client_alloc_layer(client);
scrollbar->handle = guac_client_alloc_layer(client);
/* Init mouse event state tracking */
scrollbar->dragging_handle = 0;
/* Reposition and resize to fit parent */
guac_terminal_scrollbar_parent_resized(scrollbar,
parent_width, parent_height, visible_area);
return scrollbar;
}
void guac_terminal_scrollbar_free(guac_terminal_scrollbar* scrollbar) {
/* Free layers */
guac_client_free_layer(scrollbar->client, scrollbar->handle);
guac_client_free_layer(scrollbar->client, scrollbar->container);
/* Free scrollbar */
free(scrollbar);
}
/**
* Moves the main scrollbar layer to the position indicated within the given
* scrollbar render state, sending any necessary Guacamole instructions over
* the given socket.
*
* @param scrollbar
* The scrollbar to reposition.
*
* @param state
* The guac_terminal_scrollbar_render_state describing the new scrollbar
* position.
*
* @param socket
* The guac_socket over which any instructions necessary to perform the
* render operation should be sent.
*/
static void guac_terminal_scrollbar_move_container(
guac_terminal_scrollbar* scrollbar,
guac_terminal_scrollbar_render_state* state,
guac_socket* socket) {
/* Send scrollbar position */
guac_protocol_send_move(socket,
scrollbar->container, scrollbar->parent,
state->container_x,
state->container_y,
0);
}
/**
* Resizes and redraws the main scrollbar layer according to the given
* scrollbar render state, sending any necessary Guacamole instructions over
* the given socket.
*
* @param scrollbar
* The scrollbar to resize and redraw.
*
* @param state
* The guac_terminal_scrollbar_render_state describing the new scrollbar
* size and appearance.
*
* @param socket
* The guac_socket over which any instructions necessary to perform the
* render operation should be sent.
*/
static void guac_terminal_scrollbar_draw_container(
guac_terminal_scrollbar* scrollbar,
guac_terminal_scrollbar_render_state* state,
guac_socket* socket) {
/* Set container size */
guac_protocol_send_size(socket, scrollbar->container,
state->container_width,
state->container_height);
/* Fill container with solid color */
guac_protocol_send_rect(socket, scrollbar->container, 0, 0,
state->container_width,
state->container_height);
guac_protocol_send_cfill(socket, GUAC_COMP_SRC, scrollbar->container,
0x80, 0x80, 0x80, 0x40);
}
/**
* Moves the handle layer of the scrollbar to the position indicated within the
* given scrollbar render state, sending any necessary Guacamole instructions
* over the given socket. The handle is the portion of the scrollbar that
* indicates the current scroll value and which the user can click and drag to
* change the value.
*
* @param scrollbar
* The scrollbar associated with the handle being repositioned.
*
* @param state
* The guac_terminal_scrollbar_render_state describing the new scrollbar
* handle position.
*
* @param socket
* The guac_socket over which any instructions necessary to perform the
* render operation should be sent.
*/
static void guac_terminal_scrollbar_move_handle(
guac_terminal_scrollbar* scrollbar,
guac_terminal_scrollbar_render_state* state,
guac_socket* socket) {
/* Send handle position */
guac_protocol_send_move(socket,
scrollbar->handle, scrollbar->container,
state->handle_x,
state->handle_y,
0);
}
/**
* Resizes and redraws the handle layer of the scrollbar according to the given
* scrollbar render state, sending any necessary Guacamole instructions over
* the given socket. The handle is the portion of the scrollbar that indicates
* the current scroll value and which the user can click and drag to change the
* value.
*
* @param scrollbar
* The scrollbar associated with the handle being resized and redrawn.
*
* @param state
* The guac_terminal_scrollbar_render_state describing the new scrollbar
* handle size and appearance.
*
* @param socket
* The guac_socket over which any instructions necessary to perform the
* render operation should be sent.
*/
static void guac_terminal_scrollbar_draw_handle(
guac_terminal_scrollbar* scrollbar,
guac_terminal_scrollbar_render_state* state,
guac_socket* socket) {
/* Set handle size */
guac_protocol_send_size(socket, scrollbar->handle,
state->handle_width,
state->handle_height);
/* Fill handle with solid color */
guac_protocol_send_rect(socket, scrollbar->handle, 0, 0,
state->handle_width,
state->handle_height);
guac_protocol_send_cfill(socket, GUAC_COMP_SRC, scrollbar->handle,
0xA0, 0xA0, 0xA0, 0x8F);
}
/**
* Calculates the state of the scroll bar, given its minimum, maximum, current
* values, and the state of any dragging operation. The resulting render state
* will not be reflected graphically unless the scrollbar is flushed, and any
* resulting value will not be assigned to the scrollbar unless explicitly set
* with guac_terminal_scrollbar_set_value().
*
* @param scrollbar
* The scrollbar whose state should be calculated.
*
* @param render_state
* A pointer to an existing guac_terminal_scrollbar_render_state that will
* be populated with the calculated result.
*
* @param value
* A pointer to an existing int that will be populated with the updated
* scrollbar value.
*/
static void calculate_state(guac_terminal_scrollbar* scrollbar,
guac_terminal_scrollbar_render_state* render_state,
int* value) {
/* Use unchanged current value by default */
*value = scrollbar->value;
/* Calculate container dimensions */
render_state->container_width = GUAC_TERMINAL_SCROLLBAR_WIDTH;
render_state->container_height = scrollbar->parent_height;
/* Calculate container position */
render_state->container_x = scrollbar->parent_width
- render_state->container_width;
render_state->container_y = 0;
/* Calculate handle dimensions */
render_state->handle_width = render_state->container_width
- GUAC_TERMINAL_SCROLLBAR_PADDING*2;
/* Handle can be no bigger than the scrollbar itself */
int max_handle_height = render_state->container_height
- GUAC_TERMINAL_SCROLLBAR_PADDING*2;
/* Calculate legal delta between scroll values */
int scroll_delta;
if (scrollbar->max > scrollbar->min)
scroll_delta = scrollbar->max - scrollbar->min;
else
scroll_delta = 0;
/* Scale handle relative to visible area vs. scrolling region size */
int proportional_height = max_handle_height
* scrollbar->visible_area
/ (scroll_delta + scrollbar->visible_area);
/* Ensure handle is no smaller than minimum height */
if (proportional_height > GUAC_TERMINAL_SCROLLBAR_MIN_HEIGHT)
render_state->handle_height = proportional_height;
else
render_state->handle_height = GUAC_TERMINAL_SCROLLBAR_MIN_HEIGHT;
/* Ensure handle is no larger than maximum height */
if (render_state->handle_height > max_handle_height)
render_state->handle_height = max_handle_height;
/* Calculate handle X position */
render_state->handle_x = GUAC_TERMINAL_SCROLLBAR_PADDING;
/* Calculate handle Y range */
int min_handle_y = GUAC_TERMINAL_SCROLLBAR_PADDING;
int max_handle_y = min_handle_y + max_handle_height
- render_state->handle_height;
/* Position handle relative to mouse if being dragged */
if (scrollbar->dragging_handle) {
int dragged_handle_y = scrollbar->drag_current_y
- scrollbar->drag_offset_y;
/* Keep handle within bounds */
if (dragged_handle_y < min_handle_y)
dragged_handle_y = min_handle_y;
else if (dragged_handle_y > max_handle_y)
dragged_handle_y = max_handle_y;
render_state->handle_y = dragged_handle_y;
/* Calculate scrollbar value */
if (max_handle_y > min_handle_y) {
*value = scrollbar->min
+ (dragged_handle_y - min_handle_y)
* scroll_delta
/ (max_handle_y - min_handle_y);
}
}
/* Handle Y position is relative to current scroll value */
else if (scroll_delta > 0)
render_state->handle_y = min_handle_y
+ (max_handle_y - min_handle_y)
* (scrollbar->value - scrollbar->min)
/ scroll_delta;
/* ... unless there is only one possible scroll value */
else
render_state->handle_y = GUAC_TERMINAL_SCROLLBAR_PADDING;
}
void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar,
guac_user* user, guac_socket* socket) {
/* Get old state */
guac_terminal_scrollbar_render_state* state = &scrollbar->render_state;
/* Send scrollbar container */
guac_terminal_scrollbar_draw_container(scrollbar, state, socket);
guac_terminal_scrollbar_move_container(scrollbar, state, socket);
/* Send handle */
guac_terminal_scrollbar_draw_handle(scrollbar, state, socket);
guac_terminal_scrollbar_move_handle(scrollbar, state, socket);
}
void guac_terminal_scrollbar_flush(guac_terminal_scrollbar* scrollbar) {
guac_socket* socket = scrollbar->client->socket;
/* Get old state */
int old_value = scrollbar->value;
guac_terminal_scrollbar_render_state* old_state = &scrollbar->render_state;
/* Calculate new state */
int new_value;
guac_terminal_scrollbar_render_state new_state;
calculate_state(scrollbar, &new_state, &new_value);
/* Notify of scroll if value is changing */
if (new_value != old_value && scrollbar->scroll_handler)
scrollbar->scroll_handler(scrollbar, new_value);
/* Reposition container if moved */
if (old_state->container_x != new_state.container_x
|| old_state->container_y != new_state.container_y) {
guac_terminal_scrollbar_move_container(scrollbar, &new_state, socket);
}
/* Resize and redraw container if size changed */
if (old_state->container_width != new_state.container_width
|| old_state->container_height != new_state.container_height) {
guac_terminal_scrollbar_draw_container(scrollbar, &new_state, socket);
}
/* Reposition handle if moved */
if (old_state->handle_x != new_state.handle_x
|| old_state->handle_y != new_state.handle_y) {
guac_terminal_scrollbar_move_handle(scrollbar, &new_state, socket);
}
/* Resize and redraw handle if size changed */
if (old_state->handle_width != new_state.handle_width
|| old_state->handle_height != new_state.handle_height) {
guac_terminal_scrollbar_draw_handle(scrollbar, &new_state, socket);
}
/* Store current render state */
scrollbar->render_state = new_state;
}
void guac_terminal_scrollbar_set_bounds(guac_terminal_scrollbar* scrollbar,
int min, int max) {
/* Fit value within bounds */
if (scrollbar->value > max)
scrollbar->value = max;
else if (scrollbar->value < min)
scrollbar->value = min;
/* Update bounds */
scrollbar->min = min;
scrollbar->max = max;
}
void guac_terminal_scrollbar_set_value(guac_terminal_scrollbar* scrollbar,
int value) {
/* Fit value within bounds */
if (value > scrollbar->max)
value = scrollbar->max;
else if (value < scrollbar->min)
value = scrollbar->min;
/* Update value */
scrollbar->value = value;
}
void guac_terminal_scrollbar_parent_resized(guac_terminal_scrollbar* scrollbar,
int parent_width, int parent_height, int visible_area) {
/* Assign new dimensions */
scrollbar->parent_width = parent_width;
scrollbar->parent_height = parent_height;
scrollbar->visible_area = visible_area;
}
int guac_terminal_scrollbar_handle_mouse(guac_terminal_scrollbar* scrollbar,
int x, int y, int mask) {
/* Get container rectangle bounds */
int parent_left = scrollbar->render_state.container_x;
int parent_top = scrollbar->render_state.container_y;
int parent_right = parent_left + scrollbar->render_state.container_width;
int parent_bottom = parent_top + scrollbar->render_state.container_height;
/* Calculate handle rectangle bounds */
int handle_left = parent_left + scrollbar->render_state.handle_x;
int handle_top = parent_top + scrollbar->render_state.handle_y;
int handle_right = handle_left + scrollbar->render_state.handle_width;
int handle_bottom = handle_top + scrollbar->render_state.handle_height;
/* Handle click on handle */
if (scrollbar->dragging_handle) {
/* Update drag while mouse button is held */
if (mask & GUAC_CLIENT_MOUSE_LEFT)
scrollbar->drag_current_y = y;
/* Stop drag if mouse button is released */
else
scrollbar->dragging_handle = 0;
/* Mouse event was handled by scrollbar */
return 1;
}
else if (mask == GUAC_CLIENT_MOUSE_LEFT
&& x >= handle_left && x < handle_right
&& y >= handle_top && y < handle_bottom) {
/* Start drag */
scrollbar->dragging_handle = 1;
scrollbar->drag_offset_y = y - handle_top;
scrollbar->drag_current_y = y;
/* Mouse event was handled by scrollbar */
return 1;
}
/* Eat any events that occur within the scrollbar */
return x >= parent_left && x < parent_right
&& y >= parent_top && y < parent_bottom;
}