/*******************************************************************************
* Copyright (c) 2016 Fabio Zadrozny and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation
*******************************************************************************/
package org.eclipse.e4.ui.internal.css.swt.dom.scrollbar;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Scrollable;
/**
* Base implementation of a handler which will draw a themed scroll bar.
*/
/* default */ abstract class AbstractScrollHandler {
/**
* When a drag starts, it should be set to the initial drag pixel position
* (otherwise, it must be null).
*/
protected int fInitialDragPixel;
/**
* When a drag starts, it should be set to the initial drag mouse position
* (otherwise, it must be null).
*/
protected Point fInitialDragPosition;
/**
* The scroll bar that we're replacing.
*/
protected final ScrollBar fScrollBar;
/**
* Indicates whether the thick version of the scroll should be shown.
*/
private boolean fMouseNearScrollScrollBar;
/**
* It's set during the actual painting and reflects the positions which were
* used for drawing (and can be later consulted to get the positions for
* interacting with it).
*/
protected ScrollBarPositions fScrollBarPositions;
/**
* The rect where the handle is being drawn (or null if it's not available).
*/
protected Rectangle fHandleDrawnRect;
/**
* The settings to be used to draw the scrollbar.
*/
protected IScrollBarSettings fScrollBarSettings;
/**
* Whether the scrollbar should be visible or not. Note that ideally we
* should not have a separate property and should honor the same setting
* used for the native scrollbar, but unfortunately, it's not possible to
* change the ScrollBar instance, so, a separate property was created for
* the themed implementation.
*/
private boolean fVisible = true;
private final boolean fInitialVisible;
/**
* @param scrollBar
* The scroll bar for which we'll be drawing the themed
* replacement. Note that if it's null, nothing will be drawn.
* @param scrollBarSettings
* The settings for drawing the scrollbar.
*/
protected AbstractScrollHandler(ScrollBar scrollBar, IScrollBarSettings scrollBarSettings) {
fScrollBar = scrollBar;
boolean initialVisible = true;
if (scrollBar != null) {
initialVisible = scrollBar.getVisible();
}
this.fInitialVisible = initialVisible;
this.fScrollBarSettings = scrollBarSettings;
}
public void install(AbstractThemedScrollBarAdapter abstractThemedScrollBarAdapter) {
if (this.fScrollBar != null) {
fScrollBar.setVisible(false);
this.fScrollBar.addSelectionListener(abstractThemedScrollBarAdapter);
}
}
public void uninstall(AbstractThemedScrollBarAdapter abstractThemedScrollBarAdapter, boolean disposing) {
fInitialDragPosition = null;
fScrollBarPositions = null;
fHandleDrawnRect = null;
if (this.fScrollBar != null && !this.fScrollBar.isDisposed() && !disposing) {
this.fScrollBar.removeSelectionListener(abstractThemedScrollBarAdapter);
// Restore its initial visibility state.
// Note: don't do this if we're disposing at this moment as
// StyledText will throw a NPE.
this.fScrollBar.setVisible(fInitialVisible);
}
}
public void setVisible(boolean visible) {
this.fVisible = visible;
}
public boolean getVisible() {
return this.fVisible;
}
/**
* @return The rect where the handle is being drawn (or null if it's not
* available).
*/
public Rectangle getHandleRect() {
return this.fHandleDrawnRect;
}
/**
* @param currClientArea
* The current client area on the control.
* @return A rectangle which specifies the area under which the thicker
* version of the scrollbar should be used.
*/
public abstract Rectangle computeProximityRect(Rectangle currClientArea);
/**
* If the native scrollbar is made visible, asynchronously hides it.
* (subclasses must override -- this should not be needed when SWT provides
* an API to actually replace the scrollbar).
*/
protected void checkScrollbarInvisible() {
}
/**
*
* @param scrollable
* The scrollable to consider.
* @param currClientArea
* The currently visible client area.
* @param considerMargins
* Whether the margins of the scrollable should be removed from
* the returned rectangle.
* @return A rectangle which has the full area covered by the scroll bar.
*/
protected abstract Rectangle getFullBackgroundRect(Scrollable scrollable, Rectangle currClientArea,
boolean considerMargins);
public boolean startDragOnMouseDown(Scrollable scrollable, Point controlPos, Point currHorizontalAndTopPixel,
Rectangle currClientArea) {
if (this.fScrollBar == null || !this.fVisible || !this.fScrollBarSettings.getScrollBarThemed()) {
return false;
}
Rectangle rect = this.getHandleRect();
if (rect != null) {
if (rect.contains(controlPos.x, controlPos.y)) {
fInitialDragPosition = new Point(controlPos.x, controlPos.y);
fInitialDragPixel = getRelevantPositionFromPos(currHorizontalAndTopPixel);
return true;
}
}
return false;
}
public boolean scrollOnMouseDown(Scrollable scrollable, Point controlPos,
Rectangle currClientArea) {
if (this.fScrollBar == null || !this.fVisible || fScrollBarPositions == null
|| !this.fScrollBarSettings.getScrollBarThemed()) {
return false;
}
Rectangle rect = this.getHandleRect();
if (rect != null) {
if (rect.contains(controlPos.x, controlPos.y)) {
// If the handle is at the click position, we won't scroll.
return false;
}
}
Rectangle fullRect = this.getFullBackgroundRect(scrollable, currClientArea, true);
if (fullRect != null) {
if (fullRect.contains(controlPos.x, controlPos.y)) {
int pos = this.getRelevantPositionFromPos(controlPos);
pos = (int) fScrollBarPositions.convertFromScrollBarPosToControlPixel(pos);
int selection = this.fScrollBar.getSelection();
int pageIncrement = this.fScrollBar.getPageIncrement();
if (pos > selection) {
this.fScrollBar.setSelection(selection + pageIncrement);
notifyScrollbarSelectionChanged(scrollable, SWT.PAGE_DOWN);
} else {
this.fScrollBar.setSelection(selection - pageIncrement);
notifyScrollbarSelectionChanged(scrollable, SWT.PAGE_UP);
}
return true;
}
}
return false;
}
/**
* @return whether the mouse is currently over the scroll bar.
*/
public boolean mousePosOverScroll(Scrollable scrollable, Point controlPos) {
if (this.fScrollBar == null || !this.fVisible || fScrollBarPositions == null
|| !this.fScrollBarSettings.getScrollBarThemed()) {
return false;
}
Rectangle currClientArea = scrollable.getClientArea();
Rectangle fullRect = this.getFullBackgroundRect(scrollable, currClientArea, true);
if (fullRect != null) {
boolean ret = fullRect.contains(controlPos.x, controlPos.y);
return ret;
}
return false;
}
protected abstract int getRelevantPositionFromPos(Point styledTextPos);
public boolean isDragging() {
return fInitialDragPosition != null;
}
public boolean stopDragOnMouseUp(Scrollable scrollable) {
if (fInitialDragPosition != null) {
fInitialDragPosition = null;
return true;
}
return false;
}
/**
* Sets the current pixel as the first horizontal or vertical pixel to
* position the current client area.
*
* @param scrollable
* The current scrollable control.
* @param pixel
* The pixel which should be made the first horizontal or
* vertical pixel to position the current client area.
*/
protected abstract void setPixel(Scrollable scrollable, int pixel);
/**
* Makes a drag when a mouse move happens (the initial drag must be already
* set at this point and it should drag based on the different from the
* initial drag and the mousePos).
*
* @param scrollable
* The current scrollable control.
* @param mousePos
* The current position of the mouse.
* @return Whether the drag happened or not.
*/
public boolean dragOnMouseMove(Scrollable scrollable, Point mousePos) {
if (fInitialDragPosition != null && this.fScrollBarPositions != null && this.fScrollBar != null) {
int currentMousePos = getRelevantPositionFromPos(mousePos);
int initialMousePos = getRelevantPositionFromPos(fInitialDragPosition);
int delta = (currentMousePos - initialMousePos);
delta /= this.fScrollBarPositions.fPercentageOfClientAreaFromTotalArea;
setPixel(scrollable, fInitialDragPixel + delta);
notifyScrollbarSelectionChanged(scrollable, SWT.DRAG);
return true;
}
return false;
}
/**
* Notifies that a scroll event happened.
*
* @param scrollable
* The current scrollable control.
* @param detail
* The detail for the scroll (see
* #org.eclipse.swt.widgets.ScrollBar.wmScrollChild(long, long)).
*/
protected void notifyScrollbarSelectionChanged(Scrollable scrollable, int detail) {
Event e = new Event();
e.type = SWT.Selection;
e.x = 0;
e.y = 0;
e.button = 0;
e.stateMask = 0;
e.count = 1;
e.display = scrollable.getDisplay();
e.widget = fScrollBar;
e.time = 0;
e.data = null;
e.character = '\0';
e.detail = detail;
e.doit = true;
fScrollBar.notifyListeners(SWT.Selection, e);
}
public void paintControl(GC gc, Rectangle currClientArea, Scrollable scrollable) {
checkScrollbarInvisible();
doPaintControl(gc, currClientArea, scrollable);
}
public abstract boolean computePositions(Rectangle currClientArea, Scrollable scrollable);
protected abstract void doPaintControl(GC gc, Rectangle currClientArea, Scrollable scrollable);
public int getMouseNearScrollScrollBarWidth() {
return fScrollBarSettings.getMouseNearScrollScrollBarWidth();
}
public int getCurrentScrollBarWidth() {
if (this.fMouseNearScrollScrollBar) {
return getMouseNearScrollScrollBarWidth();
}
return this.fScrollBarSettings.getScrollBarWidth();
}
/**
* @param cursorNearScroll
* if true we'll show the thicker version of the scroll (i.e.:
* usually, when we have the mouse close to the scroll, a thicker
* version is shown and when it's far-away, a lean version is
* shown.
*/
public void setCursorNearScroll(boolean cursorNearScroll) {
this.fMouseNearScrollScrollBar = cursorNearScroll;
}
/**
* @return Whether the mouse is near the scroll.
*/
public boolean getMouseNearScrollScrollBar() {
return this.fMouseNearScrollScrollBar;
}
}