// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed 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.
package com.google.collide.client.ui.tooltip;
import com.google.collide.client.ui.menu.PositionController;
import com.google.collide.client.ui.menu.PositionController.HorizontalAlign;
import com.google.collide.client.ui.menu.PositionController.VerticalAlign;
import com.google.collide.client.ui.tooltip.Coachmark.View;
import com.google.collide.client.util.CssUtils;
import com.google.collide.client.util.Elements;
import com.google.collide.mvp.CompositeView;
import com.google.collide.mvp.UiComponent;
import com.google.gwt.resources.client.CssResource;
import elemental.css.CSSStyleDeclaration;
import elemental.html.DivElement;
import elemental.html.Element;
/**
* An object which represents a coach mark (a tooltip like notification which is often dismissable).
*/
// TODO: Generalize the class, it currently isn't so general.
public class Coachmark extends UiComponent<View> {
public static Coachmark create(
Tooltip.Resources res, Renderer renderer, PositionController.Positioner positioner) {
View view = new View(res.coachmarkCss());
return new Coachmark(view, renderer, new PositionController(positioner, view.getElement()));
}
public interface Css extends CssResource {
public String base();
public String container();
public String arrow();
public String arrowInner();
public String alert();
public String disclaimer();
public int arrowSize();
}
/**
* A renderer which renders the contents of a {@link Coachmark}.
*/
public interface Renderer {
/**
* @param container the container to which any child elements should be attached.
*/
public void render(Element container, Coachmark coachmark);
}
public static class BasicRenderer implements Renderer {
private final String text;
public BasicRenderer(String text, Coachmark coachmark) {
this.text = text;
}
@Override
public void render(Element container, Coachmark coachmark) {
container.setTextContent(text);
}
}
public static class View extends CompositeView<Void> {
private final DivElement container;
private final DivElement arrow;
private final DivElement arrowInner;
private final Css css;
public View(Css css) {
super(Elements.createDivElement(css.base()));
this.css = css;
CssUtils.setDisplayVisibility2(getElement(), false);
this.container = Elements.createDivElement(css.container());
this.arrow = Elements.createDivElement(css.arrow());
this.arrowInner = Elements.createDivElement(css.arrowInner());
arrow.appendChild(arrowInner);
getElement().appendChild(arrow);
getElement().appendChild(container);
}
public Element getArrow() {
return arrow;
}
public Element getContainer() {
return container;
}
}
private final Renderer renderer;
private final PositionController positionController;
private boolean hasRendered = false;
private Coachmark(View view, Renderer renderer, PositionController positionController) {
super(view);
this.renderer = renderer;
this.positionController = positionController;
}
public void show() {
Element element = getView().getElement();
if (!hasRendered) {
renderer.render(getView().getContainer(), this);
hasRendered = true;
}
updatePosition();
CssUtils.setDisplayVisibility2(element, true);
}
public void hide() {
Element element = getView().getElement();
CssUtils.setDisplayVisibility2(element, false);
}
private void updatePosition() {
PositionController.Positioner positioner = positionController.getPositioner();
int x = 0;
int y = getYOffset(positioner.getVerticalAlignment());
// TODO: This is megahacked together, if you try to do a coach mark get here and find
// you need better, we should refactor this so it is more general.
updateArrow(getXOffset(positioner.getHorizontalAlignment()), y);
positionController.updateElementPosition(x, y);
}
private void updateArrow(int x, int y) {
Element arrow = getView().getArrow();
CSSStyleDeclaration style = arrow.getStyle();
HorizontalAlign alignment = positionController.getPositioner().getHorizontalAlignment();
style.setLeft("auto");
style.setRight("auto");
if (alignment == HorizontalAlign.LEFT) {
style.setLeft(-(x * 2), CSSStyleDeclaration.Unit.PX);
} else {
style.setRight((x * 2), CSSStyleDeclaration.Unit.PX);
}
style.setTop(-(y * 2), CSSStyleDeclaration.Unit.PX);
}
private int getYOffset(VerticalAlign alignment) {
int arrowSize = getView().css.arrowSize();
switch (alignment) {
case TOP:
return -arrowSize;
case BOTTOM:
return arrowSize;
default:
return 0;
}
}
private int getXOffset(HorizontalAlign alignment) {
int arrowSize = getView().css.arrowSize();
switch (alignment) {
case LEFT:
return -arrowSize;
case RIGHT:
return arrowSize;
default:
return 0;
}
}
}