1/*
2 * Copyright 2012 AndroidPlot.com
3 *
4 *    Licensed under the Apache License, Version 2.0 (the "License");
5 *    you may not use this file except in compliance with the License.
6 *    You may obtain a copy of the License at
7 *
8 *        http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *    Unless required by applicable law or agreed to in writing, software
11 *    distributed under the License is distributed on an "AS IS" BASIS,
12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *    See the License for the specific language governing permissions and
14 *    limitations under the License.
15 */
16
17package com.androidplot.ui.widget;
18
19import android.graphics.*;
20import com.androidplot.exception.PlotRenderException;
21import com.androidplot.ui.*;
22import com.androidplot.util.DisplayDimensions;
23import com.androidplot.ui.XLayoutStyle;
24import com.androidplot.ui.YLayoutStyle;
25import com.androidplot.util.PixelUtils;
26
27/**
28 * A Widget is a graphical sub-element of a Plot that can be positioned relative
29 * to the bounds of the Plot.
30 */
31public abstract class Widget implements BoxModelable, Resizable {
32
33    private Paint borderPaint;
34    private Paint backgroundPaint;
35    private boolean clippingEnabled = true;
36    private BoxModel boxModel = new BoxModel();
37    private SizeMetrics sizeMetrics;
38    private DisplayDimensions plotDimensions = new DisplayDimensions();
39    private DisplayDimensions widgetDimensions = new DisplayDimensions();
40    private boolean isVisible = true;
41    private PositionMetrics positionMetrics;
42    private LayoutManager layoutManager;
43
44    public Widget(LayoutManager layoutManager, SizeMetric heightMetric, SizeMetric widthMetric) {
45        this(layoutManager, new SizeMetrics(heightMetric, widthMetric));
46    }
47
48    public Widget(LayoutManager layoutManager, SizeMetrics sizeMetrics) {
49        this.layoutManager = layoutManager;
50        SizeMetrics oldSize = this.sizeMetrics;
51        setSize(sizeMetrics);
52        onMetricsChanged(oldSize, sizeMetrics);
53    }
54
55    public DisplayDimensions getWidgetDimensions() {
56        return widgetDimensions;
57    }
58
59    public AnchorPosition getAnchor() {
60        return getPositionMetrics().getAnchor();
61    }
62
63    public void setAnchor(AnchorPosition anchor) {
64        getPositionMetrics().setAnchor(anchor);
65    }
66
67
68    /**
69     * Same as {@link #position(float, com.androidplot.ui.XLayoutStyle, float, com.androidplot.ui.YLayoutStyle, com.androidplot.ui.AnchorPosition)}
70     * but with the anchor parameter defaulted to the upper left corner.
71     * @param x
72     * @param xLayoutStyle
73     * @param y
74     * @param yLayoutStyle
75     */
76    public void position(float x, XLayoutStyle xLayoutStyle, float y, YLayoutStyle yLayoutStyle) {
77        position(x, xLayoutStyle, y, yLayoutStyle, AnchorPosition.LEFT_TOP);
78    }
79
80    /**
81     * @param x            X-Coordinate of the top left corner of element.  When using RELATIVE, must be a value between 0 and 1.
82     * @param xLayoutStyle LayoutType to use when orienting this element's X-Coordinate.
83     * @param y            Y_VALS_ONLY-Coordinate of the top-left corner of element.  When using RELATIVE, must be a value between 0 and 1.
84     * @param yLayoutStyle LayoutType to use when orienting this element's Y_VALS_ONLY-Coordinate.
85     * @param anchor       The point of reference used by this positioning call.
86     */
87    public void position(float x, XLayoutStyle xLayoutStyle, float y,
88                         YLayoutStyle yLayoutStyle, AnchorPosition anchor) {
89        setPositionMetrics(new PositionMetrics(x, xLayoutStyle, y, yLayoutStyle, anchor));
90        layoutManager.addToTop(this);
91    }
92
93    /**
94     * Can be overridden by subclasses to respond to resizing events.
95     *
96     * @param oldSize
97     * @param newSize
98     */
99    protected void onMetricsChanged(SizeMetrics oldSize, SizeMetrics newSize) {
100    }
101
102    /**
103     * Can be overridden by subclasses to handle any final resizing etc. that
104     * can only be done after XML configuration etc. has completed.
105     */
106    public void onPostInit() {
107    }
108
109    /**
110     * Determines whether or not point lies within this Widget.
111     *
112     * @param point
113     * @return
114     */
115    public boolean containsPoint(PointF point) {
116        //return outlineRect != null && outlineRect.contains(point.x, point.y);
117        return widgetDimensions.canvasRect.contains(point.x, point.y);
118    }
119
120    public void setSize(SizeMetrics sizeMetrics) {
121        this.sizeMetrics = sizeMetrics;
122    }
123
124
125    public void setWidth(float width) {
126        sizeMetrics.getWidthMetric().setValue(width);
127    }
128
129    public void setWidth(float width, SizeLayoutType layoutType) {
130        sizeMetrics.getWidthMetric().set(width, layoutType);
131    }
132
133    public void setHeight(float height) {
134        sizeMetrics.getHeightMetric().setValue(height);
135    }
136
137    public void setHeight(float height, SizeLayoutType layoutType) {
138        sizeMetrics.getHeightMetric().set(height, layoutType);
139    }
140
141    public SizeMetric getWidthMetric() {
142        return sizeMetrics.getWidthMetric();
143    }
144
145    public SizeMetric getHeightMetric() {
146        return sizeMetrics.getHeightMetric();
147    }
148
149    public float getWidthPix(float size) {
150        return sizeMetrics.getWidthMetric().getPixelValue(size);
151    }
152
153    public float getHeightPix(float size) {
154        return sizeMetrics.getHeightMetric().getPixelValue(size);
155    }
156
157    public RectF getMarginatedRect(RectF widgetRect) {
158        return boxModel.getMarginatedRect(widgetRect);
159    }
160
161    public RectF getPaddedRect(RectF widgetMarginRect) {
162        return boxModel.getPaddedRect(widgetMarginRect);
163    }
164
165    public void setMarginRight(float marginRight) {
166        boxModel.setMarginRight(marginRight);
167    }
168
169    public void setMargins(float left, float top, float right, float bottom) {
170        boxModel.setMargins(left, top, right, bottom);
171    }
172
173    public void setPadding(float left, float top, float right, float bottom) {
174        boxModel.setPadding(left, top, right, bottom);
175    }
176
177    public float getMarginTop() {
178        return boxModel.getMarginTop();
179    }
180
181    public void setMarginTop(float marginTop) {
182        boxModel.setMarginTop(marginTop);
183    }
184
185    public float getMarginBottom() {
186        return boxModel.getMarginBottom();
187    }
188
189    @Override
190    public float getPaddingLeft() {
191        return boxModel.getPaddingLeft();
192    }
193
194    @Override
195    public void setPaddingLeft(float paddingLeft) {
196        boxModel.setPaddingLeft(paddingLeft);
197    }
198
199    @Override
200    public float getPaddingTop() {
201        return boxModel.getPaddingTop();
202    }
203
204    @Override
205    public void setPaddingTop(float paddingTop) {
206        boxModel.setPaddingTop(paddingTop);
207    }
208
209    @Override
210    public float getPaddingRight() {
211        return boxModel.getPaddingRight();
212    }
213
214    @Override
215    public void setPaddingRight(float paddingRight) {
216        boxModel.setPaddingRight(paddingRight);
217    }
218
219    @Override
220    public float getPaddingBottom() {
221        return boxModel.getPaddingBottom();
222    }
223
224    @Override
225    public void setPaddingBottom(float paddingBottom) {
226        boxModel.setPaddingBottom(paddingBottom);
227    }
228
229    @SuppressWarnings("SameParameterValue")
230    public void setMarginBottom(float marginBottom) {
231        boxModel.setMarginBottom(marginBottom);
232    }
233
234    public float getMarginLeft() {
235        return boxModel.getMarginLeft();
236    }
237
238    public void setMarginLeft(float marginLeft) {
239        boxModel.setMarginLeft(marginLeft);
240    }
241
242    public float getMarginRight() {
243        return boxModel.getMarginRight();
244    }
245
246    /**
247     * Causes the pixel dimensions used for rendering this Widget
248     * to be recalculated.  Should be called any time a parameter that factors
249     * into this Widget's size or position is altered.
250     */
251    public synchronized void refreshLayout() {
252        if(positionMetrics == null) {
253            // make sure positionMetrics have been set.  this method can be
254            // automatically called during xml configuration of certain params
255            // before the widget is fully configured.
256            return;
257        }
258        float elementWidth = getWidthPix(plotDimensions.paddedRect.width());
259        float elementHeight = getHeightPix(plotDimensions.paddedRect.height());
260        PointF coords = getElementCoordinates(elementHeight,
261                elementWidth, plotDimensions.paddedRect, positionMetrics);
262
263        RectF widgetRect = new RectF(coords.x, coords.y,
264                coords.x + elementWidth, coords.y + elementHeight);
265        RectF marginatedWidgetRect = getMarginatedRect(widgetRect);
266        RectF paddedWidgetRect = getPaddedRect(marginatedWidgetRect);
267        widgetDimensions = new DisplayDimensions(widgetRect,
268                marginatedWidgetRect, paddedWidgetRect);
269    }
270
271    @Override
272    public synchronized void layout(final DisplayDimensions plotDimensions) {
273        this.plotDimensions = plotDimensions;
274        refreshLayout();
275    }
276
277    public PointF getElementCoordinates(float height, float width, RectF viewRect, PositionMetrics metrics) {
278            float x = metrics.getXPositionMetric().getPixelValue(viewRect.width()) + viewRect.left;
279            float y = metrics.getYPositionMetric().getPixelValue(viewRect.height()) + viewRect.top;
280            PointF point = new PointF(x, y);
281            return PixelUtils.sub(point, getAnchorOffset(width, height, metrics.getAnchor()));
282        }
283
284    public static PointF getAnchorOffset(float width, float height, AnchorPosition anchorPosition) {
285            PointF point = new PointF();
286            switch (anchorPosition) {
287                case LEFT_TOP:
288                    break;
289                case LEFT_MIDDLE:
290                    point.set(0, height / 2);
291                    break;
292                case LEFT_BOTTOM:
293                    point.set(0, height);
294                    break;
295                case RIGHT_TOP:
296                    point.set(width, 0);
297                    break;
298                case RIGHT_BOTTOM:
299                    point.set(width, height);
300                    break;
301                case RIGHT_MIDDLE:
302                    point.set(width, height / 2);
303                    break;
304                case TOP_MIDDLE:
305                    point.set(width / 2, 0);
306                    break;
307                case BOTTOM_MIDDLE:
308                    point.set(width / 2, height);
309                    break;
310                case CENTER:
311                    point.set(width / 2, height / 2);
312                    break;
313                default:
314                    throw new IllegalArgumentException("Unsupported anchor location: " + anchorPosition);
315            }
316            return point;
317        }
318
319    public static PointF getAnchorCoordinates(RectF widgetRect, AnchorPosition anchorPosition) {
320            return PixelUtils.add(new PointF(widgetRect.left, widgetRect.top),
321                    getAnchorOffset(widgetRect.width(), widgetRect.height(), anchorPosition));
322        }
323
324        public static PointF getAnchorCoordinates(float x, float y, float width, float height, AnchorPosition anchorPosition) {
325            return getAnchorCoordinates(new RectF(x, y, x+width, y+height), anchorPosition);
326        }
327
328    public void draw(Canvas canvas, RectF widgetRect) throws PlotRenderException {
329        //outlineRect = widgetRect;
330        if (isVisible()) {
331            if (backgroundPaint != null) {
332                drawBackground(canvas, widgetDimensions.canvasRect);
333            }
334
335            /* RectF marginatedRect = new RectF(outlineRect.left + marginLeft,
336          outlineRect.top + marginTop,
337          outlineRect.right - marginRight,
338          outlineRect.bottom - marginBottom);*/
339
340            /*RectF marginatedRect = boxModel.getMarginatedRect(widgetRect);
341            RectF paddedRect = boxModel.getPaddedRect(marginatedRect);*/
342            doOnDraw(canvas, widgetDimensions.paddedRect);
343
344            if (borderPaint != null) {
345                drawBorder(canvas, widgetDimensions.paddedRect);
346            }
347        }
348    }
349
350    protected void drawBorder(Canvas canvas, RectF paddedRect) {
351        canvas.drawRect(paddedRect, borderPaint);
352    }
353
354    protected void drawBackground(Canvas canvas, RectF widgetRect) {
355        canvas.drawRect(widgetRect, backgroundPaint);
356    }
357
358    /**
359     * @param canvas     The Canvas to draw onto
360     * @param widgetRect the size and coordinates of this widget
361     */
362    protected abstract void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException;
363
364    public Paint getBorderPaint() {
365        return borderPaint;
366    }
367
368    public void setBorderPaint(Paint borderPaint) {
369        this.borderPaint = borderPaint;
370    }
371
372    public Paint getBackgroundPaint() {
373        return backgroundPaint;
374    }
375
376    public void setBackgroundPaint(Paint backgroundPaint) {
377        this.backgroundPaint = backgroundPaint;
378    }
379
380    public boolean isClippingEnabled() {
381        return clippingEnabled;
382    }
383
384    public void setClippingEnabled(boolean clippingEnabled) {
385        this.clippingEnabled = clippingEnabled;
386    }
387
388    public boolean isVisible() {
389        return isVisible;
390    }
391
392    public void setVisible(boolean visible) {
393        isVisible = visible;
394    }
395
396    public PositionMetrics getPositionMetrics() {
397        return positionMetrics;
398    }
399
400    public void setPositionMetrics(PositionMetrics positionMetrics) {
401        this.positionMetrics = positionMetrics;
402    }
403}
404