1 module fluid.future.context; 2 3 import std.meta; 4 import std.traits; 5 6 import fluid.types; 7 import fluid.tree : TreeAction; 8 import fluid.future.stack; 9 import fluid.future.static_id; 10 11 @safe: 12 13 struct TreeContext { 14 15 TreeContextData* ptr; 16 17 alias ptr this; 18 19 /// Create the context if it doesn't already exist. 20 void prepare() { 21 22 if (ptr is null) { 23 ptr = new TreeContextData(); 24 } 25 26 } 27 28 bool opCast(T : bool)() const { 29 30 return ptr !is null; 31 32 } 33 34 } 35 36 struct TreeContextData { 37 38 public { 39 40 /// Keeps track of currently active I/O systems. 41 TreeIOContext io; 42 43 /// Manages and runs tree actions. 44 TreeActionContext actions; 45 46 } 47 48 private { 49 int _lockTint; 50 auto _tint = Color(0xff, 0xff, 0xff, 0xff); 51 } 52 53 /// Tint is a transitive styling property that can be used to reduce color intensity of everything 54 /// that a node draws. Tint applies per channel, which means it can be used to reduce opacity (by changing 55 /// the alpha channel) and any of the three RGB colors. 56 /// 57 /// A tint of value `0` sets intensity to 0% (disable). A tint of value `255` sets intensity to 100% (no change). 58 /// 59 /// See_Also: 60 /// `Style.tint` 61 /// Returns: The current tint. 62 Color tint() const nothrow { 63 return _tint; 64 } 65 66 package (fluid) 67 Color tint(Color newValue) nothrow { 68 if (_lockTint > 0) { 69 return _tint; 70 } 71 else { 72 return _tint = newValue; 73 } 74 } 75 76 /// Lock tint in place, preventing it from changing, or cancel a lock, making changes possible again. 77 /// 78 /// This function is needed for compatibility with the legacy `FluidBackend` system. 79 /// Locks can be stacked, so if `lockTint()` is called twice, `unlockTint()` also has to be called twice 80 /// to unlock tinting. 81 /// 82 /// It is expected that this function will be deprecated as soon as `FluidBackend` is no longer a part 83 /// of Fluid. It will then be deleted in the next minor release. 84 void lockTint() { 85 _lockTint++; 86 } 87 88 /// ditto 89 void unlockTint() { 90 if (_lockTint > 0) { 91 _lockTint--; 92 } 93 } 94 95 } 96 97 /// Active context for I/O operations. Keeps track of currently active systems for each I/O interface. 98 /// 99 /// I/O systems are changed by a replace operation. `replace` takes the new I/O systems, but returns the one set 100 /// previously. This can be used to manage I/Os as a stack: 101 /// 102 /// --- 103 /// auto previous = io.replace(id, this); 104 /// scope (exit) io.replace(id, previous); 105 /// --- 106 struct TreeIOContext { 107 108 import std.range; 109 import std.algorithm : completeSort; 110 111 struct IOInstance { 112 IOID id; 113 IO io; 114 int opCmp(const IOInstance rhs) const { 115 return id.opCmp(rhs.id); 116 } 117 int opCmp(const IOID rhs) const { 118 return id.opCmp(rhs); 119 } 120 } 121 122 /// Key-value pairs of active I/O systems. Each pair contains the system and the ID of the interface 123 /// it implements. Pairs are sorted by the interface ID. 124 private SortedRange!(IOInstance[]) activeIOs; 125 126 /// Returns: 127 /// The active instance of the given IO interface. 128 /// `null`, no instance of this interface is currently active. 129 /// Params: 130 /// id = ID of the IO interface to load. 131 IO get(IOID id) { 132 133 auto range = activeIOs.equalRange(id); 134 if (range.empty) { 135 return null; 136 } 137 else { 138 return range.front.io; 139 } 140 141 } 142 143 /// ditto 144 T get(T)() { 145 146 const id = ioID!T; 147 return cast(T) get(id); 148 149 } 150 151 /// Set currently active I/O instance for a set interface. 152 /// Params: 153 /// id = ID of the IO interface the instance implements. 154 /// system = System to activate. 155 /// Returns: 156 /// Returns *previously set* I/O instance. 157 IO replace(IOID id, IO system) { 158 159 auto range = activeIOs.equalRange(id); 160 auto instance = IOInstance(id, system); 161 162 // Nothing set, add a new value 163 if (range.empty) { 164 IOInstance[1] instanceRange = instance; 165 completeSort(activeIOs, instanceRange[]); 166 activeIOs = assumeSorted(activeIOs.release ~ instanceRange[]); 167 return null; 168 } 169 170 // Override the previous result 171 else { 172 auto previous = range.front; 173 range.front = instance; 174 return previous.io; 175 } 176 177 } 178 179 /// Iterate on all active I/O systems. 180 /// 181 /// Elements are passed by value and cannot be modified. 182 /// 183 /// Returns: 184 /// A sorted input range of `(IOID id, IO io)` pairs. 185 auto opIndex() { 186 187 // `map` should prevent modifications 188 import std.algorithm : map; 189 190 return activeIOs.save.map!(a => a).assumeSorted; 191 192 } 193 194 /// Create a copy of the context. 195 /// 196 /// This creates a shallow clone: I/O systems can be replaced in the copy without affecting the original 197 /// and vice-versa. The individual systems will *not* be copied. 198 /// 199 /// This is useful if the I/O stack created at some specific point in time has to be reproduced elsewhere. 200 /// For example, in a drag-and-drop scenario, while a node is being dragged, it does not have a place as a child 201 /// of any node. A copy of the I/O stack it had at the start is used to continue drawing while the node 202 /// is "in air". 203 /// 204 /// Returns: 205 /// A shallow copy of the I/O context. 206 TreeIOContext dup() { 207 return TreeIOContext(activeIOs.release.dup.assumeSorted); 208 } 209 210 } 211 212 enum isIO(T) = is(T == interface) 213 && is(T : IO) 214 && !is(T == IO); 215 216 /// Get all IOs implemented by the given type 217 alias allIOs(T) = Filter!(isIO, InterfacesTuple!T); 218 219 interface HasContext { 220 221 /// Returns: The current tree context. 222 inout(TreeContext) treeContext() inout nothrow; 223 224 } 225 226 interface IO : HasContext { 227 228 bool opEquals(const Object) const; 229 230 /// Load a resource by reference. This is the same as `Node.load`. 231 /// Params: 232 /// resource = Resource to load. It will be updated with identifying information. 233 void loadTo(this This, T)(ref T resource) { 234 235 auto io = cast(This) this; 236 237 // Load the resource 238 const id = io.load(resource); 239 240 // Pass data into the resource 241 resource.load(io, id); 242 243 } 244 245 } 246 247 IOID ioID(T)() 248 if (isIO!T) { 249 250 return IOID(staticID!T); 251 252 } 253 254 /// ID for an I/O interface. 255 struct IOID { 256 257 StaticID id; 258 259 int opCmp(const IOID rhs) const { 260 return id.opCmp(rhs.id); 261 } 262 263 } 264 265 /// Keeps track of currently active actions. 266 struct TreeActionContext { 267 268 import std.array; 269 270 private { 271 272 struct RunningAction { 273 274 TreeAction action; 275 int generation; 276 277 bool isStopped() const { 278 return action.generation > generation; 279 } 280 281 } 282 283 /// Currently running actions. 284 Appender!(RunningAction[]) _actions; 285 286 /// Number of running iterators. Removing tree actions will only happen if there is exactly one 287 /// running iterator, as to not break the other ones. 288 /// 289 /// Multiple iterators may run in case a tree action draws nodes on its own: one iterator triggers 290 /// the action, and the drawn node activates another iterator. 291 int _runningIterators; 292 293 } 294 295 /// Start a number of tree actions. As the node tree is drawn, the action's hook will be called whenever 296 /// a relevant place is reached in the tree. 297 /// 298 /// To stop a running action, call the action's `stop` method. Most tree actions will do it automatically 299 /// as soon as their job is finished. 300 /// 301 /// If the action is already running, the previous run will be aborted. The action can only run once at a time. 302 /// 303 /// Params: 304 /// actions = Actions to spawn. 305 void spawn(TreeAction[] actions...) { 306 307 _actions.reserve(_actions[].length + actions.length); 308 309 // Start every action and run the hook 310 foreach (action; actions) { 311 312 _actions ~= RunningAction(action, ++action.generation); 313 action.started(); 314 315 316 } 317 318 } 319 320 /// List all currently active actions in a loop. 321 int opApply(int delegate(TreeAction) @safe yield) { 322 323 // Update the iterator counter 324 _runningIterators++; 325 scope (exit) _runningIterators--; 326 327 bool kept; 328 329 // Iterate on active actions 330 // Do *not* increment if an action was removed 331 for (size_t i = 0; i < _actions[].length; i += kept) { 332 333 auto action = _actions[][i]; 334 kept = true; 335 336 // If there's one running iterator, remove it from the array 337 // Don't pass stopped actions to the iterator 338 if (action.isStopped) { 339 340 if (_runningIterators == 1) { 341 _actions[][i] = _actions[][$-1]; 342 _actions.shrinkTo(_actions[].length - 1); 343 kept = false; 344 } 345 continue; 346 347 } 348 349 // Run the hook 350 if (auto result = yield(action.action)) return result; 351 352 } 353 354 return 0; 355 356 } 357 358 }