package de.lessvoid.nifty; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.WillClose; import org.bushe.swing.event.EventService; import org.bushe.swing.event.EventServiceExistsException; import org.bushe.swing.event.EventServiceLocator; import org.bushe.swing.event.EventTopicSubscriber; import org.bushe.swing.event.ProxySubscriber; import org.bushe.swing.event.ThreadSafeEventService; import org.bushe.swing.event.annotation.ReferenceStrength; import de.lessvoid.nifty.controls.StandardControl; import de.lessvoid.nifty.effects.EffectEventId; import de.lessvoid.nifty.elements.Action; import de.lessvoid.nifty.elements.Element; import de.lessvoid.nifty.elements.ElementMoveAction; import de.lessvoid.nifty.elements.ElementRemoveAction; import de.lessvoid.nifty.elements.EndOfFrameElementAction; import de.lessvoid.nifty.input.NiftyInputMapping; import de.lessvoid.nifty.input.NiftyMouseInputEvent; import de.lessvoid.nifty.input.keyboard.KeyboardInputEvent; import de.lessvoid.nifty.input.mouse.MouseInputEventProcessor; import de.lessvoid.nifty.layout.Box; import de.lessvoid.nifty.layout.BoxConstraints; import de.lessvoid.nifty.layout.LayoutPart; import de.lessvoid.nifty.loaderv2.ControllerFactory; import de.lessvoid.nifty.loaderv2.NiftyLoader; import de.lessvoid.nifty.loaderv2.RootLayerFactory; import de.lessvoid.nifty.loaderv2.types.ControlDefinitionType; import de.lessvoid.nifty.loaderv2.types.ElementType; import de.lessvoid.nifty.loaderv2.types.LayerType; import de.lessvoid.nifty.loaderv2.types.NiftyType; import de.lessvoid.nifty.loaderv2.types.PopupType; import de.lessvoid.nifty.loaderv2.types.RegisterEffectType; import de.lessvoid.nifty.loaderv2.types.RegisterMusicType; import de.lessvoid.nifty.loaderv2.types.RegisterSoundType; import de.lessvoid.nifty.loaderv2.types.ResourceBundleType; import de.lessvoid.nifty.loaderv2.types.StyleType; import de.lessvoid.nifty.loaderv2.types.resolver.style.StyleResolver; import de.lessvoid.nifty.loaderv2.types.resolver.style.StyleResolverDefault; import de.lessvoid.nifty.render.NiftyImage; import de.lessvoid.nifty.render.NiftyMouseImpl; import de.lessvoid.nifty.render.NiftyRenderEngine; import de.lessvoid.nifty.render.NiftyRenderEngineImpl; import de.lessvoid.nifty.screen.Screen; import de.lessvoid.nifty.screen.ScreenController; import de.lessvoid.nifty.sound.SoundSystem; import de.lessvoid.nifty.spi.input.InputSystem; import de.lessvoid.nifty.spi.render.RenderDevice; import de.lessvoid.nifty.spi.render.RenderFont; import de.lessvoid.nifty.spi.sound.SoundDevice; import de.lessvoid.nifty.spi.time.TimeProvider; import de.lessvoid.nifty.tools.FlipFlop; import de.lessvoid.nifty.tools.SizeValue; import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader; import de.lessvoid.xml.tools.BundleInfo; import de.lessvoid.xml.tools.BundleInfoBasename; import de.lessvoid.xml.tools.BundleInfoResourceBundle; import de.lessvoid.xml.tools.SpecialValuesReplace; import de.lessvoid.xml.xpp3.Attributes; /** * The main Nifty class. * * @author void */ public class Nifty { @Nonnull private static final Logger log = Logger.getLogger(Nifty.class.getName()); @Nonnull private final NiftyRenderEngine renderEngine; @Nonnull private final SoundSystem soundSystem; @Nonnull private final InputSystem inputSystem; @Nonnull private final TimeProvider timeProvider; @Nonnull private final NiftyResourceLoader resourceLoader; @Nonnull private final NiftyLoader loader; @Nonnull private final NiftyMouseImpl niftyMouse; @Nonnull private final MouseInputEventProcessor mouseInputEventProcessor; @Nonnull private final Map<String, Screen> screens; @Nonnull private final Map<String, PopupType> popupTypes; @Nonnull private final Map<String, Element> popups; @Nonnull private final Map<String, StyleType> styles; /** * When nifty loads a new style also the styles of controls need to be * updated.The only way to do this is to collect here all the style id of updated * styles and then fire an event with eventbus * @see Nifty#loadStyleFile(java.lang.String) * @see Nifty#registerStyle(de.lessvoid.nifty.loaderv2.types.StyleType) * for further details. */ @Nonnull private final Set<String> controlStylesChanged; @Nonnull private final Map<String, ControlDefinitionType> controlDefinitions; @Nonnull private final Map<String, RegisterEffectType> registeredEffects; @Nonnull private final Map<String, ScreenController> registeredScreenControllers; @Nonnull private final ControllerFactory controllerFactory; @Nonnull private final FlipFlop<List<DelayedMethodInvoke>> delayedMethodInvokes; @Nonnull private final FlipFlop<List<EndOfFrameElementAction>> endOfFrameElementActions; @Nonnull private Locale locale; /** * The screen that is currently displayed by the Nifty-GUI. This is {@code null} in case no screen is shown right * now. */ @Nullable private Screen currentScreen; @Nullable private String currentLoaded; private boolean exit; private boolean resolutionChanged; private final Set<String> closedPopups = new HashSet<String>(); @Nonnull private final List<ClosePopUp> closePopupList = new ArrayList<ClosePopUp>(); @Nullable private String alternateKeyForNextLoadXml; private long lastTime; private boolean gotoScreenInProgress; @Nullable private String alternateKey; @Nonnull private final Map<String, BundleInfo> resourceBundles = new HashMap<String, BundleInfo>(); @Nullable private Properties globalProperties; @Nonnull private final RootLayerFactory rootLayerFactory = new RootLayerFactory(); @Nonnull private final NiftyInputConsumerImpl niftyInputConsumer = new NiftyInputConsumerImpl(); private NiftyInputConsumerNotify niftyInputConsumerNotify = new NiftyInputConsumerNotifyDefault(); @Nonnull private final SubscriberRegistry subscriberRegister = new SubscriberRegistry(); private boolean debugOptionPanelColors; @Nonnull private Clipboard clipboard; /* * when set to true Nifty will ignore all mouse events. */ private boolean ignoreMouseEvents; /* * when set to true Nifty will ignore all keyboard events. */ private boolean ignoreKeyboardEvents; // set to true when NiftyMethodInvoker should throw exceptions (true) instead of only logging them (false) private boolean niftyMethodInvokerDebugEnabled; public Nifty( @Nonnull final RenderDevice newRenderDevice, @Nonnull final SoundDevice newSoundDevice, @Nonnull final InputSystem newInputSystem, @Nonnull final TimeProvider newTimeProvider) { screens = new HashMap<String, Screen>(); popupTypes = new HashMap<String, PopupType>(); popups = new HashMap<String, Element>(); styles = new HashMap<String, StyleType>(); controlDefinitions = new HashMap<String, ControlDefinitionType>(); registeredEffects = new HashMap<String, RegisterEffectType>(); registeredScreenControllers = new HashMap<String, ScreenController>(); controllerFactory = new ControllerFactory(); controlStylesChanged = new HashSet<String>(); delayedMethodInvokes = new FlipFlop<List<DelayedMethodInvoke>>( new ArrayList<DelayedMethodInvoke>(), new ArrayList<DelayedMethodInvoke>()); endOfFrameElementActions = new FlipFlop<List<EndOfFrameElementAction>>( new ArrayList<EndOfFrameElementAction>(), new ArrayList<EndOfFrameElementAction>()); resourceLoader = new NiftyResourceLoader(); newRenderDevice.setResourceLoader(resourceLoader); newSoundDevice.setResourceLoader(resourceLoader); newInputSystem.setResourceLoader(resourceLoader); renderEngine = new NiftyRenderEngineImpl(newRenderDevice); soundSystem = new SoundSystem(newSoundDevice); inputSystem = newInputSystem; timeProvider = newTimeProvider; mouseInputEventProcessor = new MouseInputEventProcessor(); niftyMouse = new NiftyMouseImpl(newRenderDevice, newInputSystem, newTimeProvider); loader = new NiftyLoader(this, timeProvider); locale = Locale.getDefault(); try { Class.forName("java.awt.datatransfer.Clipboard", false, Nifty.class.getClassLoader()); clipboard = new ClipboardAWT(); } catch (Throwable e) { log.warning("unable to access class 'java.awt.datatransfer.Clipboard'. clipboard will be disabled."); clipboard = new ClipboardInternal(); } initializeLoaderSchemas(); NiftyDefaults.initDefaultEffects(this); initializeEventBus(); lastTime = timeProvider.getMsTime(); } public String getVersion() { String result = "N/A"; ByteArrayOutputStream out = new ByteArrayOutputStream(); InputStream stream = Nifty.class.getClassLoader().getResourceAsStream("version"); try { byte[] buffer = new byte[1024]; int len; while ((len = stream.read(buffer)) > 0) { out.write(buffer, 0, len); } result = out.toString("ISO-8859-1"); } catch (Exception e) { log.log(Level.WARNING, "unable to read version file from classpath", e); } finally { try { if (stream != null) { stream.close(); } } catch (IOException e) { log.log(Level.WARNING, "unable to close version file from classpath stream. this is a bit odd", e); } } return result; } /** * Fetch the schema files the loader uses to validate the XML files. */ private void initializeLoaderSchemas() { loaderLoadSchema("nifty.nxs"); loaderLoadSchema("nifty-styles.nxs"); loaderLoadSchema("nifty-controls.nxs"); } private void loaderLoadSchema(@Nonnull final String schemaName) { try { final InputStream stream = getResourceAsStream(schemaName); if (stream == null) { throw new IOException("Failed to open stream to schema resource \"" + schemaName + "\"."); } loader.registerSchema(schemaName, stream); } catch (Exception e) { log.log(Level.SEVERE, "Failed to load the schema \"" + schemaName + "\" for the NiftyLoader", e); } } private void initializeEventBus() { try { if (EventServiceLocator.getEventService("NiftyEventBus") == null) { EventServiceLocator.setEventService("NiftyEventBus", new ThreadSafeEventService()); } } catch (EventServiceExistsException e) { log.log(Level.SEVERE, "Initialization failure. EventBus failed to initialize.", e); } } @Nonnull public EventService getEventService() { @Nullable EventService service = EventServiceLocator.getEventService("NiftyEventBus"); if (service == null) { log.severe("NiftyEventBus service was not found. Problem during initialization is likely."); return EventServiceLocator.getEventBusService(); } return service; } public void publishEvent(@Nonnull final String id, @Nonnull final NiftyEvent event) { getEventService().publish(id, event); } public void subscribeAnnotations(@Nonnull final Object object) { NiftyEventAnnotationProcessor.process(object); } public void unsubscribeAnnotations(@Nonnull final Object object) { NiftyEventAnnotationProcessor.unprocess(object); } public <T, S extends EventTopicSubscriber<? extends T>> void subscribe( @Nonnull final Screen screen, @Nonnull final String elementId, @Nonnull final Class<T> eventClass, @Nonnull final S subscriber) { ClassSaveEventTopicSubscriber theSubscriber = new ClassSaveEventTopicSubscriber(elementId, subscriber, eventClass); getEventService().subscribeStrongly(elementId, theSubscriber); log.fine("-> subscribe [" + elementId + "] screen [" + screen + "] -> [" + theSubscriber + "(" + subscriber + ")," + "(" + eventClass + ")]"); subscriberRegister.register(screen, elementId, theSubscriber); } public void unsubscribe(@Nullable final String elementId, final Object object) { // This handles direct subscription if (object instanceof EventTopicSubscriber<?>) { if (elementId == null) { log.warning("trying to unsubscribe events for an element with elementId = null. this won't work. offending " + "object \"" + object + "\". try to find the offending element and give it an id!"); return; } getEventService().unsubscribe(elementId, (EventTopicSubscriber<?>) object); log.fine("<- unsubscribe [" + elementId + "] -> [" + object + "]"); } } public void unsubscribeScreen(@Nonnull final Screen screen) { subscriberRegister.unsubscribeScreen(screen); } public void unsubscribeElement(@Nonnull final Screen screen, @Nonnull final String elementId) { subscriberRegister.unsubscribeElement(screen, elementId); } public void setAlternateKeyForNextLoadXml(@Nullable final String alternateKeyForNextLoadXmlParam) { alternateKeyForNextLoadXml = alternateKeyForNextLoadXmlParam; } /** * Update Nifty. * * @return true when nifty has finished processing the screen and false when rendering should continue. */ public boolean update() { if (currentScreen != null) { mouseInputEventProcessor.begin(); inputSystem.forwardEvents(niftyInputConsumer); if (mouseInputEventProcessor.hasLastMouseDownEvent()) { forwardMouseEventToScreen(mouseInputEventProcessor.getLastMouseDownEvent(), currentScreen); } } handleDynamicElements(); updateSoundSystem(); if (currentScreen != null) { if (log.isLoggable(Level.FINEST)) { log.finest(currentScreen.debugOutput()); } else if (log.isLoggable(Level.FINER)) { log.fine(currentScreen.debugOutputFocusElements()); } } return exit; } private boolean forwardMouseEventToScreen( @Nonnull final NiftyMouseInputEvent mouseEvent, @Nonnull final Screen screen) { // update the nifty mouse that keeps track of the current mouse position too niftyMouse.updateMousePosition(mouseEvent.getMouseX(), mouseEvent.getMouseY()); // and forward the event to the current screen return screen.mouseEvent(mouseEvent); } /** * Render Nifty. * * @param clearScreen true if nifty should clean the screen and false when you've done that already. */ public void render(final boolean clearScreen) { renderEngine.beginFrame(); if (clearScreen) { renderEngine.clear(); } renderEngine.applyAbsoluteClip(); if (currentScreen != null) { currentScreen.renderLayers(renderEngine); } if (exit) { renderEngine.clear(); } renderEngine.endFrame(); // now that the frame is complete we can reset the render device in case of the resolution change if (resolutionChanged) { resolutionChanged = false; displayResolutionChanged(); } } private void updateSoundSystem() { long current = timeProvider.getMsTime(); int delta = (int) (current - lastTime); soundSystem.update(delta); lastTime = current; } public void resetMouseInputEvents() { niftyInputConsumer.resetMouseDown(); mouseInputEventProcessor.reset(); if (currentScreen != null) { currentScreen.resetMouseDown(); } } private void handleDynamicElements() { while (hasDynamics()) { invokeMethods(); closePopUps(); removeLayerElements(); executeEndOfFrameElementActionsInternal(); } } private boolean hasDynamics() { return hasInvokeMethods() || hasClosePopups() || hasRemoveLayerElements() || hasEndOfFrameElementActions(); } private boolean hasRemoveLayerElements() { if (currentScreen == null) { return false; } return currentScreen.hasDynamicElements(); } private void removeLayerElements() { if (currentScreen != null) { currentScreen.processAddAndRemoveLayerElements(); } } private boolean hasClosePopups() { return !closePopupList.isEmpty(); } private void closePopUps() { if (hasClosePopups()) { if (currentScreen == null) { closePopupList.clear(); return; } ArrayList<ClosePopUp> copy = new ArrayList<ClosePopUp>(closePopupList); closePopupList.clear(); for (int i = 0; i < copy.size(); i++) { ClosePopUp closePopup = copy.get(i); closePopup.close(); } } } private void executeEndOfFrameElementActionsInternal() { if (hasEndOfFrameElementActions()) { endOfFrameElementActions.flip(); final List<EndOfFrameElementAction> workingCopy = endOfFrameElementActions.getSecond(); final int size = workingCopy.size(); for (int i = 0; i < size; i++) { workingCopy.get(i).perform(); } workingCopy.clear(); } } /** * @deprecated Calling this function from anywhere outside Nifty is a bad idea in all cases. Nothing good comes * from it. */ @Deprecated public void executeEndOfFrameElementActions() { log.warning("executeEndOfFrameElementActions() is a method that is basically the root of all evil. If you need " + "to use it, your application most likely has a real bad design flaw. The trouble you can cause using this " + "function is... big."); executeEndOfFrameElementActionsInternal(); } private boolean hasEndOfFrameElementActions() { return !endOfFrameElementActions.getFirst().isEmpty(); } /** * Initialize this Nifty instance from the given xml file. * * @param filename filename to nifty xml * @param startScreen screen to start exec */ public void fromXml(@Nonnull final String filename, @Nonnull final String startScreen) { prepareScreens(filename); loadFromFile(filename); gotoScreen(startScreen); } /** * Initialize this Nifty instance from the given xml file. * * @param filename filename to nifty xml */ public void fromXmlWithoutStartScreen(@Nonnull final String filename) { prepareScreens(filename); loadFromFile(filename); } /** * Initialize this Nifty instance from the given xml file. * * @param filename filename to nifty xml * @param startScreen screen to start exec * @param controllers controllers to use */ public void fromXml( @Nonnull final String filename, @Nonnull final String startScreen, final ScreenController... controllers) { registerScreenController(controllers); prepareScreens(filename); loadFromFile(filename); gotoScreen(startScreen); } /** * fromXml. * * @param fileId fileId * @param input inputStream * @param startScreen screen to start */ public void fromXml( @Nonnull final String fileId, @Nonnull final InputStream input, @Nonnull final String startScreen) { prepareScreens(fileId); loadFromStream(input); gotoScreen(startScreen); } /** * fromXmlWithoutStartScreen. * * @param fileId fileId * @param input inputStream */ public void fromXmlWithoutStartScreen(@Nonnull final String fileId, @Nonnull final InputStream input) { prepareScreens(fileId); loadFromStream(input); } /** * fromXml with ScreenControllers. * * @param fileId fileId * @param input inputStream * @param startScreen screen to start * @param controllers controllers to use */ public void fromXml( @Nonnull final String fileId, @Nonnull final InputStream input, @Nonnull final String startScreen, @Nonnull final ScreenController... controllers) { registerScreenController(controllers); prepareScreens(fileId); loadFromStream(input); gotoScreen(startScreen); } /** * Load an additional xml file without removing any of the data that might already been loaded. * * @param filename the file to load */ public void addXml(@Nonnull final String filename) { loadFromFile(filename); } /** * Load an additional xml from a stream without removing any of the data that might already been loaded. * * @param stream the stream to load */ public void addXml(@Nonnull @WillClose final InputStream stream) { loadFromStream(stream); } /** * Load and validate the given filename. If the file is valid, nothing happens. If it * is invalid you'll get an exception explaining the error. * * @param filename filename to check * @throws Exception exception describing the error */ public void validateXml(@Nonnull final String filename) throws Exception { final InputStream stream = getResourceAsStream(filename); if (stream == null) { throw new IOException("Failed to open stream to resource \"" + filename + "\" for validating."); } validateXml(stream); } /** * Load and validate the given stream. If the stream is valid, nothing happens. If it * is invalid you'll get an exception explaining the error. * * @param stream the stream of the XML to check * @throws Exception exception describing the error */ public void validateXml(@Nonnull @WillClose final InputStream stream) throws Exception { loader.validateNiftyXml("nifty.xsd", stream); } /** * load from the given file. * * @param filename filename to load */ void loadFromFile(@Nonnull final String filename) { log.fine("loadFromFile [" + filename + "]"); try { long start = timeProvider.getMsTime(); final InputStream stream = getResourceAsStream(filename); if (stream == null) { throw new IOException("Failed to open stream to resource \"" + filename + "\" for loading."); } NiftyType niftyType = loader.loadNiftyXml("nifty.nxs", stream); niftyType.create(this, timeProvider); if (log.isLoggable(Level.FINE)) { log.fine(niftyType.output()); } long end = timeProvider.getMsTime(); log.fine("loadFromFile took [" + (end - start) + "]"); } catch (Exception e) { throw new RuntimeException(e); } } /** * load from the given file. * * @param stream stream to load */ void loadFromStream(@Nonnull @WillClose final InputStream stream) { log.fine("loadFromStream []"); try { long start = timeProvider.getMsTime(); NiftyType niftyType = loader.loadNiftyXml("nifty.nxs", stream); niftyType.create(this, timeProvider); if (log.isLoggable(Level.FINE)) { log.fine(niftyType.output()); } long end = timeProvider.getMsTime(); log.fine("loadFromStream took [" + (end - start) + "]"); } catch (Exception e) { throw new RuntimeException(e); } } /** * prepare/reset screens. * * @param xmlId xml id */ void prepareScreens(@Nonnull final String xmlId) { renderEngine.screensClear(screens.values()); screens.clear(); // this.currentScreen = null; this.currentLoaded = xmlId; this.exit = false; } /** * goto screen command. this will send first an endScreen event to the current screen. * * @param id the new screen id we should go to. */ public void gotoScreen(@Nonnull final String id) { if (gotoScreenInProgress) { log.fine("gotoScreen [" + id + "] aborted because still in gotoScreenInProgress phase"); return; } log.fine("gotoScreen [" + id + "]"); gotoScreenInProgress = true; if (currentScreen == null) { gotoScreenInternal(id); } else { // end current screen currentScreen.endScreen(new EndNotify() { @Override public void perform() { gotoScreenInternal(id); } }); } } /** * goto new screen. * * @param id the new screen id we should go to. */ private void gotoScreenInternal(@Nonnull final String id) { log.fine("gotoScreenInternal [" + id + "]"); // When someone calls nifty.closePopup() directly followed by a nifty.gotoScreen() the gotoScreen will now win and // we don't wait for the pending Popups to be closed. We'll simply remove the close Popup events since they would be // gone anyway on the new Screen. This is done because the close popups are handled at the end of frame when we // might already be on the new Screen. // // If the user wants to actually see the popup to be closed (maybe because he has added some effects) then now he'll // use the closePopup() method with the EndNotify and call nifty.gotoScreen() when the EndNotify fires. if (hasClosePopups()) { ArrayList<ClosePopUp> copy = new ArrayList<ClosePopUp>(closePopupList); closePopupList.clear(); for (int i = 0; i < copy.size(); i++) { ClosePopUp closePopup = copy.get(i); closePopup.forcedCloseWithoutEndNotify(); } } currentScreen = screens.get(id); if (currentScreen == null) { log.warning("screen [" + id + "] not found"); gotoScreenInProgress = false; return; } // start the new screen if (alternateKeyForNextLoadXml != null) { currentScreen.setAlternateKey(alternateKeyForNextLoadXml); alternateKeyForNextLoadXml = null; } currentScreen.startScreen(new EndNotify() { @Override public void perform() { gotoScreenInProgress = false; } }); } /** * Set alternate key for all screen. This could be used to change behavior on all screens. * * @param alternateKey the new alternate key to use */ public void setAlternateKey(@Nullable final String alternateKey) { this.alternateKey = alternateKey; for (Screen screen : screens.values()) { screen.setAlternateKey(alternateKey); } } /** * Returns a collection of the name of all screens * * @return sn The collection containing the name of all screens */ @Nonnull public Collection<String> getAllScreensName() { Collection<String> sn = new LinkedList<String>(); for (Screen screen : screens.values()) { sn.add(screen.getScreenId()); } return sn; } public void removeScreen(@Nonnull final String id) { if (currentScreen != null) { if (currentScreen.getScreenId().equals(id)) { currentScreen.endScreen(new EndNotify() { @Override public void perform() { currentScreen = null; removeScreenInternal(id); } }); return; } removeScreenInternal(id); } } private void removeScreenInternal(@Nonnull final String id) { Screen screen = screens.remove(id); if (screen == null) { log.log(Level.SEVERE, "Internal delete of screen \"" + id + "\" failed: Screen instance not found."); } else { renderEngine.screenRemoved(screen); if (screen.getLayerElements().size() == 0) { return; } for (int i = 0; i < screen.getLayerElements().size(); i++) { removeElement(screen, screen.getLayerElements().get(i)); } } } /** * This returns all the style names currently registered with nifty. * * @return Collection of all style names */ @Nonnull public Collection<String> getAllStylesName() { return styles.keySet(); } /** * exit. */ public void exit() { if (currentScreen == null) { return; } currentScreen.endScreen( new EndNotify() { @Override public final void perform() { exit = true; currentScreen = null; } }); } public void resolutionChanged() { resolutionChanged = true; } private void displayResolutionChanged() { getRenderEngine().displayResolutionChanged(); resetMouseInputEvents(); int newWidth = getRenderEngine().getWidth(); int newHeight = getRenderEngine().getHeight(); for (Screen screen : screens.values()) { updateLayoutPart(screen.getRootElement().getLayoutPart(), newWidth, newHeight); for (Element e : screen.getLayerElements()) { updateLayoutPart(e.getLayoutPart(), newWidth, newHeight); } screen.resetLayout(); } for (Element e : popups.values()) { updateLayoutPart(e.getLayoutPart(), newWidth, newHeight); } if (currentScreen != null) { currentScreen.layoutLayers(); } } private void updateLayoutPart(@Nonnull final LayoutPart layoutPart, final int width, final int height) { Box box = layoutPart.getBox(); box.setWidth(width); box.setHeight(height); BoxConstraints boxConstraints = layoutPart.getBoxConstraints(); boxConstraints.setWidth(SizeValue.px(width)); boxConstraints.setHeight(SizeValue.px(height)); } /** * get a specific screen. * * @param id the id of the screen to retrieve. * @return the screen */ @Nullable public Screen getScreen(@Nonnull final String id) { Screen screen = screens.get(id); if (screen == null) { log.warning("screen [" + id + "] not found"); return null; } return screen; } /** * Get the SoundSystem. * * @return SoundSystem */ @Nonnull public SoundSystem getSoundSystem() { return soundSystem; } /** * Return the RenderDevice. * * @return RenderDevice */ @Nonnull public NiftyRenderEngine getRenderEngine() { return renderEngine; } /** * Get current screen. * * @return current screen */ @Nullable public Screen getCurrentScreen() { return currentScreen; } /** * Check if nifty displays the file with the given filename and is at a screen with the given screenId. * * @param filename filename * @param screenId screenId * @return true if the given screen is active and false when not */ public boolean isActive(@Nonnull final String filename, @Nonnull final String screenId) { if (currentLoaded != null && currentLoaded.equals(filename)) { if ((currentScreen != null) && currentScreen.getScreenId().equals(screenId)) { return true; } } return false; } /** * popup. * * @param popup popup */ public void registerPopup(@Nonnull final PopupType popup) { popupTypes.put(popup.getAttributes().get("id"), popup); } /** * show popup in the given screen. * * @param screen screen * @param id id */ public void showPopup( @Nonnull final Screen screen, @Nonnull final String id, @Nullable final Element defaultFocusElement) { @Nullable Element popup = popups.get(id); if (popup == null) { log.warning("missing popup [" + id + "] o_O"); } else { screen.addPopup(popup, defaultFocusElement); } } /** * Create a popup from its type parameters. * * @param screen the screen the popup is supposed to be shown on * @param popupTypeParam the type parameters * @param id the id of the popup * @return the newly created popup or {@code null} in case there is currently no active screen that could receive * the popup */ @Nonnull private Element createPopupFromType( @Nonnull final Screen screen, @Nonnull final PopupType popupTypeParam, @Nonnull final String id) { LayoutPart layerLayout = rootLayerFactory.createRootLayerLayoutPart(this); PopupType popupType = new PopupType(popupTypeParam); popupType.prepare(this, screen, screen.getRootElement().getElementType()); Element element = popupType.create(screen.getRootElement(), this, screen, layerLayout); element.setId(id); fixupSubIds(element, id); if (screen.isBound()) { element.layoutElements(); element.bindControls(screen); } return element; } private void fixupSubIds(@Nonnull final Element element, @Nonnull final String parentId) { String currentId = element.getId(); if (currentId != null && currentId.startsWith("#")) { currentId = parentId + currentId; element.setId(currentId); } if (currentId == null) { currentId = parentId; } for (int i = 0; i < element.getChildren().size(); i++) { Element e = element.getChildren().get(i); fixupSubIds(e, currentId); } } @Nullable public Element createPopup(@Nonnull final String popupId) { return createPopupWithId(popupId, NiftyIdCreator.generate()); } @Nonnull public Element createPopup(@Nonnull final Screen screen, @Nonnull final String popupId) { return createPopupWithId(screen, popupId, NiftyIdCreator.generate()); } @Nullable public Element createPopupWithId(@Nonnull final String popupId, @Nonnull final String id) { return createPopupWithStyle(popupId, id, null, null); } @Nonnull public Element createPopupWithId( @Nonnull final Screen screen, @Nonnull final String popupId, @Nonnull final String id) { return createPopupWithStyle(screen, popupId, id, null, null); } @Nullable public Element createPopupWithStyle(@Nonnull final String popupId, @Nullable final String style) { return createPopupWithStyle(popupId, NiftyIdCreator.generate(), style); } @Nonnull public Element createPopupWithStyle( @Nonnull final Screen screen, @Nonnull final String popupId, @Nullable final String style) { return createPopupWithStyle(screen, popupId, NiftyIdCreator.generate(), style); } @Nullable public Element createPopupWithStyle( @Nonnull final String popupId, @Nonnull final String id, @Nullable final String style) { return createPopupWithStyle(popupId, id, style, null); } @Nonnull public Element createPopupWithStyle( @Nonnull final Screen screen, @Nonnull final String popupId, @Nonnull final String id, @Nullable final String style) { return createPopupWithStyle(screen, popupId, id, style, null); } @Nullable public Element createPopupWithStyle( @Nonnull final String popupId, @Nullable final String style, @Nullable final Attributes parameters) { return createPopupWithStyle(popupId, NiftyIdCreator.generate(), style, parameters); } @Nonnull public Element createPopupWithStyle( @Nonnull final Screen screen, @Nonnull final String popupId, @Nullable final String style, @Nullable final Attributes parameters) { return createPopupWithStyle(screen, popupId, NiftyIdCreator.generate(), style, parameters); } @Nullable public Element createPopupWithStyle( @Nonnull final String popupId, @Nonnull final String id, @Nullable final String style, @Nullable final Attributes parameters) { final Screen screen = getCurrentScreen(); if (screen == null) { return null; } return createPopupWithStyle(screen, popupId, id, style, parameters); } @Nonnull public Element createPopupWithStyle( @Nonnull final Screen screen, @Nonnull final String popupId, @Nonnull final String id, @Nullable final String style, @Nullable final Attributes parameters) { @Nullable PopupType popupType = popupTypes.get(popupId); if (popupType == null) { throw new IllegalArgumentException("Popup ID \"" + popupId + "\" can't be matched to a popup type."); } popupType = popupType.copy(); if (style != null) { popupType.getAttributes().set("style", style); } if (parameters != null) { popupType.getAttributes().merge(parameters); } return createAndAddPopup(screen, id, popupType); } @Nonnull private Element createAndAddPopup( @Nonnull final Screen screen, @Nonnull final String id, @Nonnull final PopupType popupType) { Element popupElement = createPopupFromType(screen, popupType, id); popups.put(id, popupElement); return popupElement; } public Element findPopupByName(final String id) { return popups.get(id); } @Nullable public Element getTopMostPopup() { if (currentScreen != null) { return currentScreen.getTopMostPopup(); } return null; } /** * Close the Popup with the given id. * * @param id id of popup to close */ public void closePopup(@Nonnull final String id) { closePopupInternal(id, null); } /** * Close the Popup with the given id. This calls the given EndNotify when the onEndScreen of the popup ends. * * @param id id of popup to close * @param closeNotify EndNotify callback */ public void closePopup(@Nonnull final String id, @Nullable final EndNotify closeNotify) { closePopupInternal(id, closeNotify); } private void closePopupInternal(@Nonnull final String id, @Nullable final EndNotify closeNotify) { Element popup = popups.get(id); if (popup == null) { log.warning("missing popup [" + id + "] o_O"); return; } if (closedPopups.contains(id)) { log.fine("popup [" + id + "] already scheduled to be closed. Additional close call ignored."); return; } closedPopups.add(id); popup.resetAllEffects(); popup.startEffect(EffectEventId.onEndScreen, new EndNotify() { @Override public void perform() { closePopupList.add(new ClosePopUp(id, closeNotify)); } }); } /** * Add a new control. * * @param screen the screen the control is connected to * @param parent the parent element of the control * @param standardControl the standard control that acts as template * @return the newly created element */ @Nonnull public Element addControl( @Nonnull final Screen screen, @Nonnull final Element parent, @Nonnull final StandardControl standardControl) { try { final Element newControl = standardControl.createControl(this, screen, parent); if (screen.isBound()) { newControl.bindControls(screen); newControl.initControls(false); } if (screen.isRunning()) { newControl.startEffect(EffectEventId.onStartScreen); newControl.startEffect(EffectEventId.onActive); newControl.onStartScreen(); } return newControl; } catch (Exception e) { throw new RuntimeException(e); } } public void removeElement(@Nonnull final Screen screen, @Nonnull final Element element) { removeElement(screen, element, null); } public void removeElement( @Nonnull final Screen screen, @Nonnull final Element element, @Nullable final EndNotify endNotify) { element.startEffect(EffectEventId.onEndScreen, new EndNotify() { @Override public void perform() { scheduleEndOfFrameElementAction(new ElementRemoveAction(screen, element), endNotify); } }); } public void moveElement( @Nonnull final Screen screen, @Nonnull final Element elementToMove, @Nonnull final Element destination, @Nullable final EndNotify endNotify) { elementToMove.removeFromFocusHandler(); scheduleEndOfFrameElementAction(new ElementMoveAction(elementToMove, destination), endNotify); } /** * @deprecated Contains useless arguments, use * {@link #scheduleEndOfFrameElementAction(de.lessvoid.nifty.elements.Action, EndNotify)} */ @Deprecated public void scheduleEndOfFrameElementAction( @Nonnull final Screen screen, @Nonnull final Element element, @Nonnull final Action action, @Nullable final EndNotify endNotify) { scheduleEndOfFrameElementAction(action, endNotify); } public void scheduleEndOfFrameElementAction( @Nonnull final Action action, @Nullable final EndNotify endNotify) { endOfFrameElementActions.getFirst().add(new EndOfFrameElementAction(action, endNotify)); } /** * @return the mouseInputEventQueue */ @Nonnull public MouseInputEventProcessor getMouseInputEventQueue() { return mouseInputEventProcessor; } /** * Register ScreenController instances. * * @param controllers ScreenController */ public void registerScreenController(@Nonnull final ScreenController... controllers) { final int size = controllers.length; for (int i = 0; i < size; i++) { @Nonnull final ScreenController c = controllers[i]; registeredScreenControllers.put(c.getClass().getName(), c); } } /** * find a ScreenController instance that matches the given controllerClass name. * * @param controllerClass controller class name * @return ScreenController instance */ @Nullable public ScreenController findScreenController(@Nonnull final String controllerClass) { return registeredScreenControllers.get(controllerClass); } /** * Remove screen controller instances. * * @param controllers the instances to remove */ public void unregisterScreenController(@Nonnull final ScreenController... controllers) { final int size = controllers.length; for (int i = 0; i < size; i++) { @Nonnull final ScreenController c = controllers[i]; registeredScreenControllers.remove(c.getClass().getName()); } } /** * Obtain the controllerFactory, used to register and unregister Factories * that create controllers. * <p> * @return */ @Nonnull public ControllerFactory getControllerFactory() { return controllerFactory; } @Nonnull public NiftyLoader getLoader() { return loader; } @Nonnull public TimeProvider getTimeProvider() { return timeProvider; } public class ClosePopUp { @Nonnull private final String removePopupId; @Nullable private final EndNotify closeNotify; public ClosePopUp(@Nonnull final String popupId, @Nullable final EndNotify closeNotifyParam) { removePopupId = popupId; closeNotify = closeNotifyParam; } public void close() { close(closeNotify); } public void forcedCloseWithoutEndNotify() { close(null); } private void close(@Nullable final EndNotify endNotify) { if (currentScreen != null) { @Nullable Element popup = popups.get(removePopupId); if (popup != null) { currentScreen.closePopup(popup, endNotify); } } } } public void addScreen(@Nonnull final String id, @Nonnull final Screen screen) { screens.put(id, screen); renderEngine.screenAdded(screen); } public void registerStyle(@Nonnull final StyleType style) { final String styleId = style.getStyleId(); log.fine("registerStyle " + styleId); // Handle the simple, normal case. // This is a new style, register it and return early. if (!styles.containsKey(styleId)) { styles.put(styleId, style); return; } // This style has already been registered. // Log a warning and re-register it, overriding the previous value. log.warning("Style: " + styleId + " was already registered. The " + "new definition will override the previous."); styles.put(styleId, style); // Handle re-registration of control styles (containing a '#') // & regular styles. if (styleId.contains("#")) { // A sub-style has been changed. We need to take its base style name that // is the first part of the name of this style (basename#subname). final String simpleId = styleId.split("#")[0]; // We could fire the event here, but in case that more than one sub-style // is changed we will send the same event for each. So we are batching it // here and then fire an event in loadStyles method. controlStylesChanged.add(simpleId); } else { // This is a regular style, so just fire the event now. getEventService().publish("style-refresh:" + styleId, styleId); } } public void registerControlDefintion(@Nonnull final ControlDefinitionType controlDefinition) { controlDefinitions.put(controlDefinition.getName(), controlDefinition); // TODO: add the same behaviour of register style and try to updating // already registered control defintions. } public void registerEffect(@Nonnull final RegisterEffectType registerEffectType) { registeredEffects.put(registerEffectType.getName(), registerEffectType); } @Nullable public ControlDefinitionType resolveControlDefinition(@Nullable final String name) { if (name == null) { return null; } return controlDefinitions.get(name); } @Nullable public RegisterEffectType resolveRegisteredEffect(@Nullable final String name) { if (name == null) { return null; } return registeredEffects.get(name); } @Nonnull public StyleResolver getDefaultStyleResolver() { return new StyleResolverDefault(styles); } @Nullable public String getAlternateKey() { return alternateKey; } public void delayedMethodInvoke(@Nonnull final NiftyDelayedMethodInvoke method, @Nonnull final Object... params) { delayedMethodInvokes.getFirst().add(new DelayedMethodInvoke(method, params)); } public void invokeMethods() { if (hasInvokeMethods()) { delayedMethodInvokes.flip(); final List<DelayedMethodInvoke> workingList = delayedMethodInvokes.getSecond(); // process the working copy final int count = workingList.size(); for (int i = 0; i < count; i++) { workingList.get(i).perform(); } // clear the secondary list workingList.clear(); } } private boolean hasInvokeMethods() { return !delayedMethodInvokes.getFirst().isEmpty(); } private static class DelayedMethodInvoke { @Nonnull private final NiftyDelayedMethodInvoke method; @Nonnull private final Object[] params; public DelayedMethodInvoke(@Nonnull final NiftyDelayedMethodInvoke method, @Nonnull final Object... params) { this.method = method; this.params = params; } public void perform() { method.performInvoke(params); } } public void setLocale(@Nonnull final Locale locale) { this.locale = locale; getEventService().publish(new NiftyLocaleChangedEvent(locale)); if (resourceBundles.size() > 0) { log.log(Level.WARNING, "Changing the locale will not effect ALL loaded resource bundles. TextRenderer should work now tho :)"); } } public Locale getLocale() { return locale; } @Nonnull public Map<String, BundleInfo> getResourceBundles() { return resourceBundles; } public void addResourceBundle(@Nonnull final String id, @Nonnull final String filename) { resourceBundles.put(id, new BundleInfoBasename(filename)); } public void addResourceBundle(@Nonnull final String id, @Nonnull final ResourceBundle resourceBundle) { BundleInfo bundleInfo = resourceBundles.get(id); if (bundleInfo != null && bundleInfo instanceof BundleInfoResourceBundle) { ((BundleInfoResourceBundle) bundleInfo).add(resourceBundle); return; } resourceBundles.put(id, new BundleInfoResourceBundle(resourceBundle)); } @Nullable public Properties getGlobalProperties() { return globalProperties; } public void setGlobalProperties(@Nullable final Properties globalProperties) { this.globalProperties = globalProperties; } @Nonnull public RootLayerFactory getRootLayerFactory() { return rootLayerFactory; } public void loadStyleFile(@Nonnull final String styleFile) { try { NiftyType niftyType = new NiftyType(); loader.loadStyleFile("nifty-styles.nxs", styleFile, niftyType, this); niftyType.create(this, getTimeProvider()); if (log.isLoggable(Level.FINE)) { log.fine("loadStyleFile"); log.fine(niftyType.output()); } for (String id : controlStylesChanged) { getEventService().publish("style-refresh:" + id, id); } controlStylesChanged.clear(); } catch (Exception e) { log.log(Level.WARNING, e.getMessage(), e); } } public void loadControlFile(@Nonnull final String controlFile) { try { NiftyType niftyType = new NiftyType(); loader.loadControlFile("nifty-controls.nxs", controlFile, niftyType); niftyType.create(this, getTimeProvider()); if (log.isLoggable(Level.FINE)) { log.fine("loadControlFile"); log.fine(niftyType.output()); } } catch (Exception e) { log.log(Level.WARNING, e.getMessage(), e); } } public void registerResourceBundle(@Nonnull final String id, @Nonnull final String filename) { try { NiftyType niftyType = new NiftyType(); ResourceBundleType resourceBundle = new ResourceBundleType(); resourceBundle.getAttributes().set("id", id); resourceBundle.getAttributes().set("filename", filename); niftyType.addResourceBundle(resourceBundle); niftyType.create(this, getTimeProvider()); if (log.isLoggable(Level.FINE)) { log.fine("registerResourceBundle"); log.fine(niftyType.output()); } } catch (Exception e) { log.log(Level.WARNING, e.getMessage(), e); } } public void registerEffect(@Nonnull final String name, @Nonnull final String classParam) { try { NiftyType niftyType = new NiftyType(); RegisterEffectType registerEffect = new RegisterEffectType(name, classParam); niftyType.addRegisterEffect(registerEffect); niftyType.create(this, getTimeProvider()); if (log.isLoggable(Level.FINE)) { log.fine("registerEffect"); log.fine(niftyType.output()); } } catch (Exception e) { log.log(Level.WARNING, e.getMessage(), e); } } public void registerSound(@Nonnull final String id, @Nonnull final String filename) { try { NiftyType niftyType = new NiftyType(); RegisterSoundType registerSound = new RegisterSoundType(); registerSound.getAttributes().set("id", id); registerSound.getAttributes().set("filename", filename); niftyType.addRegisterSound(registerSound); niftyType.create(this, getTimeProvider()); if (log.isLoggable(Level.FINE)) { log.fine("registerSound"); log.fine(niftyType.output()); } } catch (Exception e) { log.log(Level.WARNING, e.getMessage(), e); } } public void registerMusic(@Nonnull final String id, @Nonnull final String filename) { try { NiftyType niftyType = new NiftyType(); RegisterMusicType registerMusic = new RegisterMusicType(); registerMusic.getAttributes().set("id", id); registerMusic.getAttributes().set("filename", filename); niftyType.addRegisterMusic(registerMusic); niftyType.create(this, getTimeProvider()); if (log.isLoggable(Level.FINE)) { log.fine("registerMusic"); log.fine(niftyType.output()); } } catch (Exception e) { log.warning(e.getMessage()); } } public void registerMouseCursor( @Nonnull final String id, @Nonnull final String filename, final int hotspotX, final int hotspotY) { try { getNiftyMouse().registerMouseCursor(id, filename, hotspotX, hotspotY); } catch (IOException e) { log.log(Level.WARNING, e.getMessage(), e); } } @Nonnull public NiftyMouse getNiftyMouse() { return niftyMouse; } /** * This is now an inner class to make sure no one calls it from the outside directly. * All InputSystem processing should go through the InputSystem. * * @author void */ private class NiftyInputConsumerImpl implements NiftyInputConsumer { private boolean button0Down = false; private boolean button1Down = false; private boolean button2Down = false; @Override public boolean processMouseEvent( final int mouseX, final int mouseY, final int mouseWheel, final int button, final boolean buttonDown) { boolean processed = false; if (!isIgnoreMouseEvents()) { processed = processEvent(createEvent(mouseX, mouseY, mouseWheel, button, buttonDown)); if (log.isLoggable(Level.FINE)) { log.fine("[processMouseEvent] [" + mouseX + ", " + mouseY + ", " + mouseWheel + ", " + button + ", " + "" + buttonDown + "] processed [" + processed + "]"); } } niftyInputConsumerNotify.processedMouseEvent(mouseX, mouseY, mouseWheel, button, buttonDown, processed); return processed; } @Override public boolean processKeyboardEvent(@Nonnull final KeyboardInputEvent keyEvent) { boolean processed = false; if (!isIgnoreKeyboardEvents()) { if (currentScreen != null) { processed = currentScreen.keyEvent(keyEvent); if (log.isLoggable(Level.FINE)) { log.fine("[processKeyboardEvent] " + keyEvent + " processed [" + processed + "]"); } } } niftyInputConsumerNotify.processKeyboardEvent(keyEvent, processed); return processed; } void resetMouseDown() { button0Down = false; button1Down = false; button2Down = false; } @Nonnull private NiftyMouseInputEvent createEvent( final int mouseX, final int mouseY, final int mouseWheel, final int button, final boolean buttonDown) { switch (button) { case 0: button0Down = buttonDown; break; case 1: button1Down = buttonDown; break; case 2: button2Down = buttonDown; break; } NiftyMouseInputEvent result = new NiftyMouseInputEvent(); result.initialize(renderEngine.convertFromNativeX(mouseX), renderEngine.convertFromNativeY(mouseY), mouseWheel, button0Down, button1Down, button2Down); return result; } private boolean processEvent(@Nonnull final NiftyMouseInputEvent mouseInputEvent) { mouseInputEventProcessor.process(mouseInputEvent); if (currentScreen == null) { return false; } else { boolean handled = forwardMouseEventToScreen(mouseInputEvent, currentScreen); handleDynamicElements(); return handled; } } } /** * Helper class to connect better to the eventbus. * * @author void */ @SuppressWarnings("rawtypes") private static class ClassSaveEventTopicSubscriber implements EventTopicSubscriber, ProxySubscriber { @Nonnull private final String elementId; @Nullable private EventTopicSubscriber target; @Nonnull private final Class eventClass; private ClassSaveEventTopicSubscriber( @Nonnull final String elementId, @Nullable final EventTopicSubscriber target, @Nonnull final Class eventClass) { this.elementId = elementId; this.target = target; this.eventClass = eventClass; } @Override @Nonnull public String toString() { return super.toString() + "{" + elementId + "}{" + target + "}{" + eventClass + "}"; } @SuppressWarnings("unchecked") @Override public void onEvent(final String topic, final Object data) { if (target != null && eventClass.isInstance(data)) { target.onEvent(topic, data); } } @Nonnull public String getElementId() { return elementId; } @Nullable @Override public Object getProxiedSubscriber() { return target; } @Override public void proxyUnsubscribed() { this.target = null; } @Nonnull @Override public ReferenceStrength getReferenceStrength() { return ReferenceStrength.STRONG; } } /** * Creates an element from its type in a specific index in the list of parent * * @return the Element created */ @Nonnull public Element createElementFromType( @Nonnull final Screen screen, @Nonnull final Element parent, final ElementType type, final int index) { if (type instanceof LayerType) { return createElementFromTypeInternal(screen, parent, type, getRootLayerFactory().createRootLayerLayoutPart(this), index); } return createElementFromTypeInternal(screen, parent, type, new LayoutPart(), index); } @Nonnull public Element createElementFromType( @Nonnull final Screen screen, @Nonnull final Element parent, final ElementType type) { if (type instanceof LayerType) { return createElementFromTypeInternal(screen, parent, type, getRootLayerFactory().createRootLayerLayoutPart (this), parent.getChildren().size()); } return createElementFromTypeInternal(screen, parent, type, new LayoutPart(), parent.getChildren().size()); } @Nonnull private Element createElementFromTypeInternal( @Nonnull final Screen screen, @Nonnull final Element parent, @Nonnull final ElementType type, @Nonnull final LayoutPart layoutPart, final int index) { ElementType elementType = type.copy(); elementType.prepare(this, screen, screen.getRootElement().getElementType()); elementType.connectParentControls(parent); Element element = elementType.create(parent, this, screen, layoutPart, index); if (screen.isBound()) { //screen.layoutLayers(); element.bindControls(screen); element.initControls(false); element.startEffect(EffectEventId.onStartScreen); element.startEffect(EffectEventId.onActive); element.onStartScreen(); } return element; } /** * Create a new Image. This is a helper method so that you don't need to get the RenderEngine. * * @param name file name to use * @param filterLinear filter * @return RenderImage instance */ @Nullable public NiftyImage createImage(@Nonnull final String name, final boolean filterLinear) { final Screen screen = getCurrentScreen(); if (screen == null) { throw new IllegalStateException("Can't create a image with this method, while there is currently not active " + "screen"); } return renderEngine.createImage(screen, name, filterLinear); } /** * Create a new Image. This is a helper method so that you don't need to get the RenderEngine. * * @param screen the screen that is used to create the image * @param name file name to use * @param filterLinear filter * @return RenderImage instance */ @Nullable public NiftyImage createImage(@Nonnull final Screen screen, @Nonnull final String name, final boolean filterLinear) { return renderEngine.createImage(screen, name, filterLinear); } /** * You can set this option to true to let Nifty automatically render all panels in random * background colors for debugging purposes. * * @param option enable (true) or disable (false) this feature */ public void setDebugOptionPanelColors(final boolean option) { this.debugOptionPanelColors = option; } /** * Returns true if the debug option to render panel colors in enabled. * * @return true if the option is enabled and false if not */ public boolean isDebugOptionPanelColors() { return debugOptionPanelColors; } /** * A helper method to call the special values replace method ${} syntax * * @param value the value to perform the replace on * @return the value with stuff replaced */ @Nonnull public String specialValuesReplace(@Nullable final String value) { return SpecialValuesReplace.replace( value, getResourceBundles(), currentScreen == null ? null : currentScreen.getScreenController(), globalProperties, locale); } private class SubscriberRegistry { @Nonnull private final Map<Screen, Map<String, List<ClassSaveEventTopicSubscriber>>> screenBasedSubscribers = new HashMap<Screen, Map<String, List<ClassSaveEventTopicSubscriber>>>(); public void register(final Screen screen, final String elementId, final ClassSaveEventTopicSubscriber subscriber) { Map<String, List<ClassSaveEventTopicSubscriber>> elements = screenBasedSubscribers.get(screen); if (elements == null) { elements = new HashMap<String, List<ClassSaveEventTopicSubscriber>>(); screenBasedSubscribers.put(screen, elements); } List<ClassSaveEventTopicSubscriber> list = elements.get(elementId); if (list == null) { list = new ArrayList<ClassSaveEventTopicSubscriber>(); elements.put(elementId, list); } list.add(subscriber); } public void unsubscribeScreen(@Nonnull final Screen screen) { Map<String, List<ClassSaveEventTopicSubscriber>> elements = screenBasedSubscribers.get(screen); if (elements != null && !elements.isEmpty()) { for (Map.Entry<String, List<ClassSaveEventTopicSubscriber>> entry : elements.entrySet()) { List<ClassSaveEventTopicSubscriber> list = entry.getValue(); for (int i = 0; i < list.size(); i++) { ClassSaveEventTopicSubscriber subscriber = list.get(i); getEventService().unsubscribe(subscriber.getElementId(), subscriber); log.fine("<- unsubscribe screen for [" + screen + "] [" + subscriber.getElementId() + "] -> [" + subscriber + "]"); } list.clear(); } elements.clear(); } screenBasedSubscribers.remove(screen); } public void unsubscribeElement(@Nonnull final Screen screen, @Nonnull final String elementId) { Map<String, List<ClassSaveEventTopicSubscriber>> elements = screenBasedSubscribers.get(screen); if (elements != null && !elements.isEmpty()) { List<ClassSaveEventTopicSubscriber> list = elements.get(elementId); if (list != null && !list.isEmpty()) { for (int i = 0; i < list.size(); i++) { ClassSaveEventTopicSubscriber subscriber = list.get(i); getEventService().unsubscribe(subscriber.getElementId(), subscriber); log.fine("<- unsubscribe element [" + elementId + "] [" + subscriber.getElementId() + "] -> [" + subscriber + "]"); } list.clear(); } } } } @Nonnull public Clipboard getClipboard() { return clipboard; } public void setClipboard(@Nonnull final Clipboard clipboard) { this.clipboard = clipboard; } @Nullable public RenderFont createFont(@Nonnull final String name) { return getRenderEngine().createFont(name); } @Nonnull public String getFontname(@Nonnull final RenderFont font) { return getRenderEngine().getFontname(font); } /** * Enable automatic scaling of all GUI elements in relation to the given base resolution. * * @param baseResolutionX width, for instance 1024 * @param baseResolutionY height, for instance 768 */ public void enableAutoScaling(final int baseResolutionX, final int baseResolutionY) { renderEngine.enableAutoScaling(baseResolutionX, baseResolutionY); } public void enableAutoScaling( final int baseResolutionX, final int baseResolutionY, final float scaleX, final float scaleY) { renderEngine.enableAutoScaling(baseResolutionX, baseResolutionY, scaleX, scaleY); } public void disableAutoScaling() { renderEngine.disableAutoScaling(); } /** * Return an InputStream for the given resource name. This is resolved * using the currently registered ResourceLocations. * * @param ref the name of the resource to load * @return the InputStream of the resource data */ @Nullable public InputStream getResourceAsStream(@Nonnull final String ref) { return resourceLoader.getResourceAsStream(ref); } /** * Return the ResourceLoader of this Nifty instance. * * @return the ResourceLoader to load resources */ @Nonnull public NiftyResourceLoader getResourceLoader() { return resourceLoader; } public void setIgnoreMouseEvents(final boolean newValue) { ignoreMouseEvents = newValue; } public boolean isIgnoreMouseEvents() { return ignoreMouseEvents; } public void setIgnoreKeyboardEvents(final boolean newValue) { ignoreKeyboardEvents = newValue; } public boolean isIgnoreKeyboardEvents() { return ignoreKeyboardEvents; } public NiftyInputConsumerNotify getNiftyInputConsumerNotify() { return niftyInputConsumerNotify; } public void setNiftyInputConsumerNotify(final NiftyInputConsumerNotify newNotify) { this.niftyInputConsumerNotify = newNotify; } /** * This method clip a rectangle area. This is meant to be used outside {@code nifty.render(true) } call, it * will clip an area like a camera . <b>Note:</b> Some elements with childClip=true could modify this clip within {@code nifty.render(true) } loop. * Tested on Java2d renderer. * @param x0 X coordinates of left-upper corner * @param y0 Y coordinates of left-upper corner * @param x1 X coordinates of right-bottom corner * @param y1 Y coordinates of right-bottom corner */ public void setAbsoluteClip(int x0,int y0,int x1,int y1){ this.renderEngine.setAbsoluteClip(x0, y0, x1, y1); } /** * {@link #setAbsoluteClip(int, int, int, int) setAbsoluteClip} but this use position and size of a rectangle * @param x * @param y * @param width * @param height */ public void setAbsoluteClipRect(int x,int y,int width,int height){ this.setAbsoluteClip(x, y, x+width, y+height); } /** * Disable absolute clipping. */ public void disableAbsoluteClip() { renderEngine.disableAbsoluteClip(); } /** * Implementation of {@link NiftyInputConsumerNotify} which will just ignore everything. * * @author void */ private static class NiftyInputConsumerNotifyDefault implements NiftyInputConsumerNotify { @Override public void processedMouseEvent( int mouseX, int mouseY, int mouseWheel, int button, boolean buttonDown, boolean processed) { } @Override public void processKeyboardEvent(KeyboardInputEvent keyEvent, boolean processed) { } } public void internalPopupRemoved(final String id) { closedPopups.remove(id); } /** * Set this to true to let the NiftyMethodInvoker not catch RuntimeException/Exception when calling any interact * on click method. This way when your on click handler crashes with an Exception it will crash the whole application. * This might be helpful when you develop your application. The default value is false which will not crash the * application but instead will only log the exception. * * @param debugEnabled set to true to not catch exceptions (default value is false) */ public void setNiftyMethodInvokerDebugEnabled(final boolean debugEnabled) { this.niftyMethodInvokerDebugEnabled = debugEnabled; } public boolean isNiftyMethodInvokerDebugEnabled() { return niftyMethodInvokerDebugEnabled; } /** * Sets the static default NiftyInputMapping used by all input event handlers. * <b>Important note: this change will persist to all Nifty instances.</b> */ public static void setDefaultInputMappingType (@Nonnull final Class<? extends NiftyInputMapping> defaultInputMappingType) { NiftyDefaults.setDefaultInputMapping(defaultInputMappingType); } }