486 lines
16 KiB
C
486 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 "config.h"
|
|
#include "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;
|
|
|
|
}
|
|
|