001/** 002 * Copyright (c) 2008-2014 Ardor Labs, Inc. 003 * 004 * This file is part of Ardor3D. 005 * 006 * Ardor3D is free software: you can redistribute it and/or modify it 007 * under the terms of its license which may be found in the accompanying 008 * LICENSE file or at <http://www.ardor3d.com/LICENSE>. 009 */ 010 011package com.ardor3d.input.logical; 012 013import java.util.List; 014import java.util.Set; 015import java.util.concurrent.CopyOnWriteArraySet; 016 017import com.ardor3d.annotation.GuardedBy; 018import com.ardor3d.annotation.MainThread; 019import com.ardor3d.annotation.ThreadSafe; 020import com.ardor3d.framework.Canvas; 021import com.ardor3d.input.InputState; 022import com.ardor3d.input.PhysicalLayer; 023 024/** 025 * Implementation of a logical layer on top of the physical one, to be able to more easily trigger certain commands for 026 * certain combination of user input. 027 */ 028@ThreadSafe 029public final class LogicalLayer { 030 private final Set<InputSource> _inputs = new CopyOnWriteArraySet<>(); 031 private final Set<InputTrigger> _triggers = new CopyOnWriteArraySet<>(); 032 private LogicalTriggersApplier _applier = new BasicTriggersApplier(); 033 034 public LogicalLayer() {} 035 036 public void registerInput(final Canvas source, final PhysicalLayer physicalLayer) { 037 _inputs.add(new InputSource(source, physicalLayer)); 038 } 039 040 /** 041 * Register a trigger for evaluation when the {@link #checkTriggers(double)} method is called. 042 * 043 * @param inputTrigger 044 * the trigger to check 045 */ 046 public void registerTrigger(final InputTrigger inputTrigger) { 047 _triggers.add(inputTrigger); 048 } 049 050 /** 051 * Deregister a trigger for evaluation when the {@link #checkTriggers(double)} method is called. 052 * 053 * @param inputTrigger 054 * the trigger to stop checking 055 */ 056 public void deregisterTrigger(final InputTrigger inputTrigger) { 057 _triggers.remove(inputTrigger); 058 } 059 060 /** 061 * Check all registered triggers to see if their respective conditions are met. For every trigger whose condition is 062 * true, perform the associated action. 063 * 064 * @param tpf 065 * time per frame in seconds 066 */ 067 @MainThread 068 public synchronized void checkTriggers(final double tpf) { 069 for (final InputSource is : _inputs) { 070 is.physicalLayer.readState(); 071 072 final List<InputState> newStates = is.physicalLayer.drainAvailableStates(); 073 074 if (newStates.isEmpty()) { 075 _applier.checkAndPerformTriggers(_triggers, is.source, new TwoInputStates(is.lastState, is.lastState), 076 tpf); 077 } else { 078 // used to spread tpf evenly among triggered actions 079 final double time = newStates.size() > 1 ? tpf / newStates.size() : tpf; 080 for (final InputState inputState : newStates) { 081 // no trigger is valid in the LOST_FOCUS state, so don't bother checking them 082 if (inputState != InputState.LOST_FOCUS) { 083 _applier.checkAndPerformTriggers(_triggers, is.source, new TwoInputStates(is.lastState, 084 inputState), time); 085 } 086 087 is.lastState = inputState; 088 } 089 } 090 } 091 } 092 093 public void setApplier(final LogicalTriggersApplier applier) { 094 _applier = applier; 095 } 096 097 public LogicalTriggersApplier getApplier() { 098 return _applier; 099 } 100 101 private static class InputSource { 102 private final Canvas source; 103 private final PhysicalLayer physicalLayer; 104 @GuardedBy("LogicalLayer.this") 105 private InputState lastState; 106 107 public InputSource(final Canvas source, final PhysicalLayer physicalLayer) { 108 this.source = source; 109 this.physicalLayer = physicalLayer; 110 lastState = InputState.EMPTY; 111 } 112 } 113 114 public Set<InputTrigger> getTriggers() { 115 return _triggers; 116 } 117 118 public InputTrigger findTriggerById(final String id) { 119 for (final InputTrigger trigger : _triggers) { 120 if (id.equals(trigger.getId())) { 121 return trigger; 122 } 123 } 124 return null; 125 } 126}