/* * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Codename One designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Codename One through http://www.codenameone.com/ if you * need additional information or have any questions. */ package com.codename1.components; import com.codename1.ui.Component; import com.codename1.ui.Dialog; import com.codename1.ui.Display; import com.codename1.ui.FontImage; import com.codename1.ui.Form; import com.codename1.ui.Graphics; import com.codename1.ui.Image; import com.codename1.ui.animations.CommonTransitions; import com.codename1.ui.geom.Dimension; import com.codename1.ui.layouts.BorderLayout; import com.codename1.ui.plaf.Style; import com.codename1.ui.plaf.UIManager; import com.codename1.ui.util.WeakHashMap; /** * <p>Shows a "Washing Machine" infinite progress indication animation, to customize the image you can either * use the infiniteImage theme constant or the <code>setAnimation</code> method. The image is rotated * automatically so don't use an animated image or anything like that as it would fail with the rotation logic.</p> * * <p>This class can be used in one of two ways either by embedding the component into the UI thru something * like this: * </p> * <script src="https://gist.github.com/codenameone/bddead645fcd8ee33e9c.js"></script> * * <p> * Notice that this can be used within a custom dialog too.<br> * A second approach allows showing the infinite progress over the entire screen which blocks all input. This tints * the background while the infinite progress rotates: * </p> *<script src="https://gist.github.com/codenameone/a0a6abca781cd86e4f5e.js"></script> * <img src="https://www.codenameone.com/img/developer-guide/infinite-progress.png" alt="InfiniteProgress"> * * @author Shai Almog */ public class InfiniteProgress extends Component { private Image animation; private int angle = 0; private int tick; private WeakHashMap<Integer, Image> cache = new WeakHashMap<Integer, Image>(); private int tintColor = 0x90000000; /** * The animation rotates with EDT ticks, but not for every tick. To slow down the animation increase this * number and to speed it up reduce it to 1. It can't be 0 or lower. */ private int tickCount = 3; /** * The angle to increase (in degrees naturally) in every tick count, reduce to 1 to make the animation perfectly * slow and smooth, increase to 45 to make it fast and jumpy. Its probably best to use a number that divides well * with 360 but that isn't a requirement. Valid numbers are anything between 1 and 359. */ private int angleIncrease = 16; /** * Default constructor to define the UIID */ public InfiniteProgress() { setUIID("InfiniteProgress"); } /** * Shows the infinite progress over the whole screen, the blocking can be competed by calling <code>dispose()</code> * on the returned <code>Dialog</code>. *<script src="https://gist.github.com/codenameone/a0a6abca781cd86e4f5e.js"></script> * @return the dialog created for the blocking effect, disposing it will return to the previous form and remove the input block. */ public Dialog showInifiniteBlocking() { Form f = Display.getInstance().getCurrent(); if(f == null) { f = new Form(); f.show(); } if (f.getClientProperty("isInfiniteProgress") == null) { f.setTintColor(tintColor); } Dialog d = new Dialog(); d.putClientProperty("isInfiniteProgress", true); d.setTintColor(0x0); d.setDialogUIID("Container"); d.setLayout(new BorderLayout()); d.addComponent(BorderLayout.CENTER, this); d.setTransitionInAnimator(CommonTransitions.createEmpty()); d.setTransitionOutAnimator(CommonTransitions.createEmpty()); d.showPacked(BorderLayout.CENTER, false); return d; } /** * {@inheritDoc} */ protected void initComponent() { super.initComponent(); if(animation == null) { animation = UIManager.getInstance().getThemeImageConstant("infiniteImage"); } Form f = getComponentForm(); if(f != null) { f.registerAnimated(this); } } /** * {@inheritDoc} */ protected void deinitialize() { super.deinitialize(); Form f = getComponentForm(); if(f == null) { f = Display.getInstance().getCurrent(); } f.deregisterAnimated(this); } /** * {@inheritDoc} */ public boolean animate() { if (Display.getInstance().getCurrent() != this.getComponentForm()) { return false; } // reduce repaint thrushing of the UI from the infinite progress boolean val = super.animate() || tick % tickCount == 0; tick++; return val; } /** * {@inheritDoc} */ protected Dimension calcPreferredSize() { if(animation == null) { animation = UIManager.getInstance().getThemeImageConstant("infiniteImage"); if(animation == null) { int size = Display.getInstance().convertToPixels(7, true); String f = getUIManager().getThemeConstant("infiniteDefaultColor", null); int color = 0x777777; if(f != null) { color = Integer.parseInt(f, 16); } FontImage fi = FontImage.createFixed("" + FontImage.MATERIAL_AUTORENEW, FontImage.getMaterialDesignFont(), color, size, size, 0); animation = fi.toImage(); } } if(animation == null) { return new Dimension(100, 100); } Style s = getStyle(); return new Dimension(s.getHorizontalPadding() + animation.getWidth(), s.getVerticalPadding() + animation.getHeight()); } /** * {@inheritDoc} */ public void paint(Graphics g) { if (this.getComponentForm() != null && Display.getInstance().getCurrent() != this.getComponentForm()) { return; } super.paint(g); if(animation == null) { return; } int v = angle % 360; Style s = getStyle(); /*if(g.isAffineSupported()) { g.rotate(((float)v) / 57.2957795f, getAbsoluteX() + s.getPadding(LEFT) + getWidth() / 2, getAbsoluteY() + s.getPadding(TOP) + getHeight() / 2); g.drawImage(getAnimation(), getX() + s.getPadding(LEFT), getY() + s.getPadding(TOP)); g.resetAffine(); } else {*/ Image rotated; if(animation instanceof FontImage) { angle += angleIncrease; rotated = animation.rotate(v); } else { angle += angleIncrease; Integer angle = new Integer(v); rotated = cache.get(angle); if(rotated == null) { rotated = animation.rotate(v); cache.put(v, rotated); } } g.drawImage(rotated, getX() + s.getPaddingLeftNoRTL(), getY() + s.getPaddingTop()); //} } /** * @return the animation */ public Image getAnimation() { return animation; } /** * Allows setting the image that will be rotated as part of this effect * @param animation the animation to set */ public void setAnimation(Image animation) { this.animation = animation; cache.clear(); } /** * {@inheritDoc} */ public String[] getPropertyNames() { return new String[] {"animation"}; } /** * {@inheritDoc} */ public Class[] getPropertyTypes() { return new Class[] {Image.class}; } /** * {@inheritDoc} */ public Object getPropertyValue(String name) { if(name.equals("animation")) { return animation; } return null; } /** * {@inheritDoc} */ public String setPropertyValue(String name, Object value) { if(name.equals("animation")) { this.animation = (Image)value; cache.clear(); return null; } return super.setPropertyValue(name, value); } /** * The tinting color of the screen when the showInifiniteBlocking method is invoked * @return the tintColor */ public int getTintColor() { return tintColor; } /** * The tinting color of the screen when the showInifiniteBlocking method is invoked * @param tintColor the tintColor to set */ public void setTintColor(int tintColor) { this.tintColor = tintColor; } /** * The animation rotates with EDT ticks, but not for every tick. To slow down the animation increase this * number and to speed it up reduce it to 1. It can't be 0 or lower. * @return the tickCount */ public int getTickCount() { return tickCount; } /** * The animation rotates with EDT ticks, but not for every tick. To slow down the animation increase this * number and to speed it up reduce it to 1. It can't be 0 or lower. * @param tickCount the tickCount to set */ public void setTickCount(int tickCount) { this.tickCount = tickCount; } /** * The angle to increase (in degrees naturally) in every tick count, reduce to 1 to make the animation perfectly * slow and smooth, increase to 45 to make it fast and jumpy. Its probably best to use a number that divides well * with 360 but that isn't a requirement. Valid numbers are anything between 1 and 359. * @return the angleIncrease */ public int getAngleIncrease() { return angleIncrease; } /** * The angle to increase (in degrees naturally) in every tick count, reduce to 1 to make the animation perfectly * slow and smooth, increase to 45 to make it fast and jumpy. Its probably best to use a number that divides well * with 360 but that isn't a requirement. Valid numbers are anything between 1 and 359. * @param angleIncrease the angleIncrease to set */ public void setAngleIncrease(int angleIncrease) { this.angleIncrease = angleIncrease; } }