1 module auxil.default_visitor; 2 3 import std.typecons : Flag; 4 5 import auxil.location : Axis, Location, SizeType, Order; 6 import auxil.model : Orientation; 7 8 alias SizeEnabled = Flag!"SizeEnabled"; 9 alias TreePathEnabled = Flag!"TreePathEnabled"; 10 11 private struct Default 12 { 13 void enterTree(Order order, Data, Model)(ref const(Data) data, ref Model model) {} 14 void enterNode(Order order, Data, Model)(ref const(Data) data, ref Model model) {} 15 void leaveNode(Order order, Data, Model)(ref const(Data) data, ref Model model) {} 16 void beforeChildren(Order order, Data, Model)(ref const(Data) data, ref Model model) {} 17 void afterChildren(Order order, Data, Model)(ref const(Data) data, ref Model model) {} 18 } 19 20 alias MeasuringVisitor = MeasuringVisitorImpl!Default; 21 22 /// Visitor to measure size of tree nodes 23 struct MeasuringVisitorImpl(Derived = Default) 24 { 25 SizeType[2] size; 26 27 this(SizeType[2] s) @safe @nogc nothrow 28 { 29 size = s; 30 } 31 32 enum treePathEnabled = TreePathEnabled.no; 33 34 Orientation orientation = Orientation.Vertical; 35 Orientation old_orientation; 36 37 bool complete() @safe @nogc 38 { 39 return false; 40 } 41 42 bool engaged() @safe @nogc nothrow 43 { 44 return true; 45 } 46 47 void doEnterTree(Order order, Data, Model)(auto ref const(Data) data, ref Model model) 48 { 49 () @trusted { (cast(Derived*) &this).enterTree!(order, Data, Model)(data, model); }(); 50 } 51 52 void doEnterNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 53 { 54 static if (Model.Collapsable) 55 { 56 model.size = size[model.orientation] + model.Spacing; 57 final switch (model.orientation) 58 { 59 case Orientation.Vertical: 60 model.header_size = model.size; 61 break; 62 case Orientation.Horizontal: 63 model.header_size = size[Orientation.Vertical] + model.Spacing; 64 break; 65 } 66 old_orientation = orientation; 67 orientation = model.orientation; 68 } 69 else 70 model.size = size[orientation] + model.Spacing; 71 72 final switch (orientation) 73 { 74 // shrink size of the window 75 case Orientation.Vertical: 76 size[Orientation.Horizontal] -= size[Orientation.Vertical] + model.Spacing; 77 break; 78 case Orientation.Horizontal: 79 // do nothing 80 break; 81 } 82 83 if (engaged) 84 { 85 () @trusted { (cast(Derived*) &this).enterNode!(order, Data, Model)(data, model); }(); 86 } 87 } 88 89 void doLeaveNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 90 { 91 if (engaged) 92 { 93 () @trusted { (cast(Derived*) &this).leaveNode!(order, Data, Model)(data, model); }(); 94 } 95 96 final switch (this.orientation) 97 { 98 // restore shrinked size of the window 99 case Orientation.Vertical: 100 size[Orientation.Horizontal] += size[Orientation.Vertical] + model.Spacing; 101 break; 102 case Orientation.Horizontal: 103 // do nothing again 104 break; 105 } 106 107 static if (Model.Collapsable) 108 orientation = old_orientation; 109 } 110 111 /// returns true if the current children shouldn't be processed 112 /// but traversing should be continued 113 bool doBeforeChildren(Order order, Data, Model)(ref const(Data) data, ref Model model) 114 { 115 if (orientation == Orientation.Horizontal) 116 { 117 size[orientation] -= size[Orientation.Vertical] + model.Spacing; 118 } 119 120 () @trusted { (cast(Derived*) &this).beforeChildren!(order, Data, Model)(data, model); }(); 121 122 return false; 123 } 124 125 void doAfterChildren(Order order, Data, Model)(ref const(Data) data, ref Model model) 126 { 127 () @trusted { (cast(Derived*) &this).afterChildren!(order, Data, Model)(data, model); }(); 128 129 if (orientation == Orientation.Horizontal) 130 { 131 size[orientation] += size[Orientation.Vertical] + model.Spacing; 132 } 133 } 134 135 auto startValue(Order order)(size_t len) 136 { 137 return 0; 138 } 139 140 auto setPath(int i) 141 { 142 } 143 144 auto setChildSize(Model, ChildModel)(ref Model model, ref ChildModel child_model, int len, ref float residual) 145 { 146 final switch(model.orientation) 147 { 148 case Orientation.Horizontal: 149 double sf = cast(double)(model.size)/len; 150 SizeType sz = cast(SizeType)sf; 151 residual += sf - sz; 152 if (residual >= 1.0) 153 { 154 residual -= 1; 155 sz += 1; 156 } 157 child_model.size = sz; 158 break; 159 case Orientation.Vertical: 160 model.size += modelSize(child_model, orientation); 161 break; 162 } 163 } 164 165 private auto modelSize(Model)(ref const(Model) model, Orientation orientation) 166 { 167 static if (Model.Collapsable) 168 { 169 // if modelSizeIsValid is true the model size is defined by the model 170 // in other case it is defined by the visitor 171 static if (Model.DynamicCollapsable) 172 bool modelSizeIsValid = model.rtCollapsable; 173 else 174 bool modelSizeIsValid = true; 175 176 if (modelSizeIsValid && orientation == model.orientation) 177 return model.size; 178 } 179 return size[orientation] + model.Spacing; 180 } 181 } 182 183 struct State 184 { 185 Axis x, y; 186 Orientation orientation; 187 } 188 189 struct StateStack 190 { 191 import automem : Vector; 192 import std.experimental.allocator.mallocator : Mallocator; 193 194 Vector!(State, Mallocator) stack; 195 196 auto put(State s) @trusted @nogc 197 { 198 import std.algorithm : move; 199 stack.put(move(s)); 200 } 201 202 ref back() @nogc return 203 { 204 return stack[$-1]; 205 } 206 207 void popBack() @nogc @trusted 208 { 209 assert(!stack.empty); 210 stack.popBack; 211 } 212 } 213 214 /// Visitor for traversing tree to do useful job 215 struct TreePathVisitorImpl(Derived = Default) 216 { 217 SizeType[2] size; 218 219 @disable this(this); 220 221 this(SizeType[2] s) @safe @nogc nothrow 222 { 223 size = s; 224 } 225 226 enum treePathEnabled = TreePathEnabled.yes; 227 228 Location loc; 229 Orientation orientation = Orientation.Vertical; 230 StateStack stateStack; 231 232 bool complete() @safe @nogc 233 { 234 return loc.checkState; 235 } 236 237 bool engaged() @safe @nogc nothrow 238 { 239 return loc.stateFirstOrRest; 240 } 241 242 void doEnterTree(Order order, Data, Model)(auto ref const(Data) data, ref Model model) 243 { 244 final switch (this.orientation) 245 { 246 case Orientation.Vertical: 247 loc.x.size = size[Orientation.Horizontal] + model.Spacing; 248 static if (Model.Collapsable) 249 loc.y.size = model.header_size; 250 else 251 loc.y.size = model.size; 252 break; 253 case Orientation.Horizontal: 254 assert(0, "Not implemented"); 255 } 256 257 () @trusted { (cast(Derived*) &this).enterTree!(order, Data, Model)(data, model); }(); 258 } 259 260 void doEnterNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 261 { 262 if (!engaged) 263 { 264 static if (Model.Collapsable) 265 stateStack.put(State(loc.x, loc.y, orientation)); 266 return; 267 } 268 269 static if (order == Order.Sinking) with(loc) 270 { 271 final switch(orientation) 272 { 273 case Orientation.Vertical: 274 y.position = y.position + y.change; 275 static if (Model.Collapsable) 276 y.change = model.header_size; 277 else 278 y.change = model.size; 279 break; 280 case Orientation.Horizontal: 281 x.position = x.position + x.change; 282 static if (Model.Collapsable) 283 x.change = model.header_size; 284 else 285 x.change = model.size; 286 break; 287 } 288 289 scope(exit) 290 { 291 final switch(orientation) 292 { 293 case Orientation.Vertical: 294 if (y.position + y.change > y.destination) 295 { 296 path = current_path; 297 _state = State.finishing; 298 } 299 break; 300 case Orientation.Horizontal: 301 version(none) if (x.position + x.change > x.destination) 302 { 303 path = current_path; 304 _state = State.finishing; 305 } 306 break; 307 } 308 } 309 } 310 311 static if (Model.Collapsable) 312 { 313 stateStack.put(State(loc.x, loc.y, orientation)); 314 315 orientation = model.orientation; 316 } 317 else // static if (!Model.Collapsable) 318 { 319 final switch (orientation) 320 { 321 case Orientation.Vertical: 322 break; 323 case Orientation.Horizontal: 324 loc.x.size = model.size; 325 break; 326 } 327 scope(exit) 328 { 329 final switch (this.orientation) 330 { 331 case Orientation.Vertical: 332 break; 333 case Orientation.Horizontal: 334 loc.x.position += model.size; 335 break; 336 } 337 } 338 } 339 340 () @trusted { (cast(Derived*) &this).enterNode!(order, Data, Model)(data, model); }(); 341 } 342 343 void doLeaveNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 344 { 345 static if (Model.Collapsable) 346 { 347 orientation = stateStack.back.orientation; 348 349 if (orientation == Orientation.Vertical) 350 loc.x = stateStack.back.x; 351 if (orientation == Orientation.Horizontal) 352 loc.y = stateStack.back.y; 353 stateStack.popBack; 354 } 355 356 if (engaged) 357 { 358 static if (order == Order.Bubbling) with(loc) 359 { 360 final switch(orientation) 361 { 362 case Orientation.Vertical: 363 y.position = y.position + y.change; 364 static if (Model.Collapsable) 365 y.change = -model.header_size; 366 else 367 y.change = -model.size; 368 369 if (y.position <= y.destination) 370 { 371 _state = State.finishing; 372 path = current_path; 373 } 374 break; 375 case Orientation.Horizontal: 376 x.position = x.position + x.change; 377 static if (Model.Collapsable) 378 x.change = -model.header_size; 379 else 380 x.change = -model.size; 381 382 version(none) if (x.position <= x.destination) 383 { 384 _state = State.finishing; 385 path = current_path; 386 } 387 break; 388 } 389 } 390 391 static if (!Model.Collapsable) 392 { 393 final switch (orientation) 394 { 395 case Orientation.Vertical: 396 break; 397 case Orientation.Horizontal: 398 loc.x.size = model.size; 399 break; 400 } 401 } 402 403 () @trusted { (cast(Derived*) &this).leaveNode!(order, Data, Model)(data, model); }(); 404 } 405 } 406 407 /// returns true if the current children shouldn't be processed 408 /// but traversing should be continued 409 bool doBeforeChildren(Order order, Data, Model)(ref const(Data) data, ref Model model) 410 if (Model.Collapsable) 411 { 412 // set proper current size for children 413 if (model.orientation == Orientation.Vertical) 414 { 415 loc.y.size = loc.y.change; 416 } 417 else 418 { 419 loc.x.size = loc.y.change; 420 } 421 422 () @trusted { (cast(Derived*) &this).beforeChildren!(order, Data, Model)(data, model); }(); 423 424 static if (order == Order.Bubbling) 425 { 426 // Edge case if the start path starts from this collapsable exactly 427 // then the childs of the collapsable aren't processed 428 if (loc.path.value.length && loc.current_path.value[] == loc.path.value[]) 429 return true; 430 } 431 432 loc.indent; 433 434 with(loc) final switch (model.orientation) 435 { 436 case Orientation.Vertical: 437 x.position = x.position + model.header_size; 438 x.size = x.size - model.header_size; 439 break; 440 case Orientation.Horizontal: 441 break; 442 } 443 444 return false; 445 } 446 447 void doAfterChildren(Order order, Data, Model)(ref const(Data) data, ref Model model) 448 if (Model.Collapsable) 449 { 450 loc.unindent; 451 452 with(loc) final switch (model.orientation) 453 { 454 case Orientation.Vertical: 455 x.position = x.position - model.header_size; 456 x.size = x.size + model.header_size; 457 break; 458 case Orientation.Horizontal: 459 break; 460 } 461 462 () @trusted { (cast(Derived*) &this).afterChildren!(order, Data, Model)(data, model); }(); 463 } 464 465 auto startValue(Order order)(size_t len) 466 { 467 return loc.startValue!order(len); 468 } 469 470 auto setPath(int i) 471 { 472 loc.setPath(i); 473 } 474 475 auto setChildSize(Model, ChildModel)(ref Model model, ref ChildModel child_model, int len, ref float residual) 476 { 477 } 478 }