nsSplitterFrame.cpp (30344B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // 8 // Eric Vaughan 9 // Netscape Communications 10 // 11 // See documentation in associated header file 12 // 13 14 #include "nsSplitterFrame.h" 15 16 #include "LayoutConstants.h" 17 #include "SimpleXULLeafFrame.h" 18 #include "gfxContext.h" 19 #include "mozilla/CSSOrderAwareFrameIterator.h" 20 #include "mozilla/ComputedStyle.h" 21 #include "mozilla/MouseEvents.h" 22 #include "mozilla/PresShell.h" 23 #include "mozilla/ReflowInput.h" 24 #include "mozilla/dom/Document.h" 25 #include "mozilla/dom/Element.h" 26 #include "mozilla/dom/Event.h" 27 #include "mozilla/dom/MouseEvent.h" 28 #include "nsContainerFrame.h" 29 #include "nsContentUtils.h" 30 #include "nsDOMCSSDeclaration.h" 31 #include "nsDisplayList.h" 32 #include "nsFlexContainerFrame.h" 33 #include "nsFrameList.h" 34 #include "nsGkAtoms.h" 35 #include "nsHTMLParts.h" 36 #include "nsIDOMEventListener.h" 37 #include "nsLayoutUtils.h" 38 #include "nsNameSpaceManager.h" 39 #include "nsPresContext.h" 40 #include "nsScrollbarButtonFrame.h" 41 #include "nsStyledElement.h" 42 #include "nsXULElement.h" 43 44 using namespace mozilla; 45 46 using mozilla::dom::Element; 47 using mozilla::dom::Event; 48 49 class nsSplitterInfo { 50 public: 51 nscoord min; 52 nscoord max; 53 nscoord current; 54 nscoord pref; 55 nscoord changed; 56 nsCOMPtr<nsIContent> childElem; 57 }; 58 59 enum class ResizeType { 60 // Resize the closest sibling in a given direction. 61 Closest, 62 // Resize the farthest sibling in a given direction. 63 Farthest, 64 // Resize only flexible siblings in a given direction. 65 Flex, 66 // No space should be taken out of any children in that direction. 67 // FIXME(emilio): This is a rather odd name... 68 Grow, 69 // Only resize adjacent siblings. 70 Sibling, 71 // Don't resize anything in a given direction. 72 None, 73 }; 74 static ResizeType ResizeTypeFromAttribute(const Element& aElement, 75 nsAtom* aAttribute) { 76 static Element::AttrValuesArray strings[] = { 77 nsGkAtoms::farthest, nsGkAtoms::flex, nsGkAtoms::grow, 78 nsGkAtoms::sibling, nsGkAtoms::none, nullptr}; 79 switch (aElement.FindAttrValueIn(kNameSpaceID_None, aAttribute, strings, 80 eCaseMatters)) { 81 case 0: 82 return ResizeType::Farthest; 83 case 1: 84 return ResizeType::Flex; 85 case 2: 86 // Grow only applies to resizeAfter. 87 if (aAttribute == nsGkAtoms::resizeafter) { 88 return ResizeType::Grow; 89 } 90 break; 91 case 3: 92 return ResizeType::Sibling; 93 case 4: 94 return ResizeType::None; 95 default: 96 break; 97 } 98 return ResizeType::Closest; 99 } 100 101 class nsSplitterFrameInner final : public nsIDOMEventListener { 102 protected: 103 virtual ~nsSplitterFrameInner(); 104 105 public: 106 NS_DECL_ISUPPORTS 107 NS_DECL_NSIDOMEVENTLISTENER 108 109 explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter) 110 : mOuter(aSplitter) {} 111 112 void Disconnect() { mOuter = nullptr; } 113 114 nsresult MouseDown(Event* aMouseEvent); 115 nsresult MouseUp(Event* aMouseEvent); 116 nsresult MouseMove(Event* aMouseEvent); 117 118 void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent); 119 void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent); 120 121 void AdjustChildren(nsPresContext* aPresContext); 122 void AdjustChildren(nsPresContext* aPresContext, 123 nsTArray<nsSplitterInfo>& aChildInfos, 124 bool aIsHorizontal); 125 126 void AddRemoveSpace(nscoord aDiff, nsTArray<nsSplitterInfo>& aChildInfos, 127 int32_t& aSpaceLeft); 128 129 void ResizeChildTo(nscoord& aDiff); 130 131 void UpdateState(); 132 133 void AddListener(); 134 void RemoveListener(); 135 136 enum class State { Open, CollapsedBefore, CollapsedAfter, Dragging }; 137 enum CollapseDirection { Before, After }; 138 139 ResizeType GetResizeBefore(); 140 ResizeType GetResizeAfter(); 141 State GetState(); 142 143 bool SupportsCollapseDirection(CollapseDirection aDirection); 144 145 void EnsureOrient(); 146 void SetPreferredSize(nsIFrame* aChildBox, bool aIsHorizontal, nscoord aSize); 147 148 nsSplitterFrame* mOuter; 149 bool mDidDrag = false; 150 nscoord mDragStart = 0; 151 nsIFrame* mParentBox = nullptr; 152 bool mPressed = false; 153 nsTArray<nsSplitterInfo> mChildInfosBefore; 154 nsTArray<nsSplitterInfo> mChildInfosAfter; 155 State mState = State::Open; 156 nscoord mSplitterPos = 0; 157 bool mDragging = false; 158 159 const Element* SplitterElement() const { 160 return mOuter->GetContent()->AsElement(); 161 } 162 }; 163 164 NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener) 165 166 ResizeType nsSplitterFrameInner::GetResizeBefore() { 167 return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizebefore); 168 } 169 170 ResizeType nsSplitterFrameInner::GetResizeAfter() { 171 return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizeafter); 172 } 173 174 nsSplitterFrameInner::~nsSplitterFrameInner() = default; 175 176 nsSplitterFrameInner::State nsSplitterFrameInner::GetState() { 177 static Element::AttrValuesArray strings[] = {nsGkAtoms::dragging, 178 nsGkAtoms::collapsed, nullptr}; 179 static Element::AttrValuesArray strings_substate[] = { 180 nsGkAtoms::before, nsGkAtoms::after, nullptr}; 181 switch (SplitterElement()->FindAttrValueIn( 182 kNameSpaceID_None, nsGkAtoms::state, strings, eCaseMatters)) { 183 case 0: 184 return State::Dragging; 185 case 1: 186 switch (SplitterElement()->FindAttrValueIn( 187 kNameSpaceID_None, nsGkAtoms::substate, strings_substate, 188 eCaseMatters)) { 189 case 0: 190 return State::CollapsedBefore; 191 case 1: 192 return State::CollapsedAfter; 193 default: 194 if (SupportsCollapseDirection(After)) { 195 return State::CollapsedAfter; 196 } 197 return State::CollapsedBefore; 198 } 199 } 200 return State::Open; 201 } 202 203 // 204 // NS_NewSplitterFrame 205 // 206 // Creates a new Toolbar frame and returns it 207 // 208 nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 209 return new (aPresShell) nsSplitterFrame(aStyle, aPresShell->GetPresContext()); 210 } 211 212 NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame) 213 214 nsSplitterFrame::nsSplitterFrame(ComputedStyle* aStyle, 215 nsPresContext* aPresContext) 216 : SimpleXULLeafFrame(aStyle, aPresContext, kClassID) {} 217 218 void nsSplitterFrame::Destroy(DestroyContext& aContext) { 219 if (mInner) { 220 mInner->RemoveListener(); 221 mInner->Disconnect(); 222 mInner = nullptr; 223 } 224 SimpleXULLeafFrame::Destroy(aContext); 225 } 226 227 nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID, 228 nsAtom* aAttribute, 229 AttrModType aModType) { 230 nsresult rv = 231 SimpleXULLeafFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); 232 if (aAttribute == nsGkAtoms::state) { 233 mInner->UpdateState(); 234 } 235 236 return rv; 237 } 238 239 /** 240 * Initialize us. If we are in a box get our alignment so we know what direction 241 * we are 242 */ 243 void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 244 nsIFrame* aPrevInFlow) { 245 MOZ_ASSERT(!mInner); 246 mInner = new nsSplitterFrameInner(this); 247 248 SimpleXULLeafFrame::Init(aContent, aParent, aPrevInFlow); 249 250 mInner->AddListener(); 251 mInner->mParentBox = nullptr; 252 } 253 254 static bool IsValidParentBox(nsIFrame* aFrame) { 255 return aFrame->IsFlexContainerFrame(); 256 } 257 258 static nsIFrame* GetValidParentBox(nsIFrame* aChild) { 259 return aChild->GetParent() && IsValidParentBox(aChild->GetParent()) 260 ? aChild->GetParent() 261 : nullptr; 262 } 263 264 void nsSplitterFrame::Reflow(nsPresContext* aPresContext, 265 ReflowOutput& aDesiredSize, 266 const ReflowInput& aReflowInput, 267 nsReflowStatus& aStatus) { 268 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 269 mInner->mParentBox = GetValidParentBox(this); 270 mInner->UpdateState(); 271 } 272 return SimpleXULLeafFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, 273 aStatus); 274 } 275 276 static bool SplitterIsHorizontal(const nsIFrame* aParentBox) { 277 // If our parent is horizontal, the splitter is vertical and vice-versa. 278 MOZ_ASSERT(aParentBox->IsFlexContainerFrame()); 279 const FlexboxAxisInfo info(aParentBox); 280 return !info.mIsRowOriented; 281 } 282 283 NS_IMETHODIMP 284 nsSplitterFrame::HandlePress(nsPresContext* aPresContext, 285 WidgetGUIEvent* aEvent, 286 nsEventStatus* aEventStatus) { 287 return NS_OK; 288 } 289 290 NS_IMETHODIMP 291 nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext, 292 WidgetGUIEvent* aEvent, 293 nsEventStatus* aEventStatus, 294 bool aControlHeld) { 295 return NS_OK; 296 } 297 298 NS_IMETHODIMP 299 nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent, 300 nsEventStatus* aEventStatus) { 301 return NS_OK; 302 } 303 304 NS_IMETHODIMP 305 nsSplitterFrame::HandleRelease(nsPresContext* aPresContext, 306 WidgetGUIEvent* aEvent, 307 nsEventStatus* aEventStatus) { 308 return NS_OK; 309 } 310 311 void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 312 const nsDisplayListSet& aLists) { 313 SimpleXULLeafFrame::BuildDisplayList(aBuilder, aLists); 314 315 // if the mouse is captured always return us as the frame. 316 if (mInner->mDragging && aBuilder->IsForEventDelivery()) { 317 // XXX It's probably better not to check visibility here, right? 318 aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this); 319 return; 320 } 321 } 322 323 nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext, 324 WidgetGUIEvent* aEvent, 325 nsEventStatus* aEventStatus) { 326 NS_ENSURE_ARG_POINTER(aEventStatus); 327 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { 328 return NS_OK; 329 } 330 331 AutoWeakFrame weakFrame(this); 332 RefPtr<nsSplitterFrameInner> inner(mInner); 333 switch (aEvent->mMessage) { 334 case eMouseMove: 335 inner->MouseDrag(aPresContext, aEvent); 336 break; 337 338 case eMouseUp: 339 if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) { 340 inner->MouseUp(aPresContext, aEvent); 341 } 342 break; 343 344 default: 345 break; 346 } 347 348 NS_ENSURE_STATE(weakFrame.IsAlive()); 349 return SimpleXULLeafFrame::HandleEvent(aPresContext, aEvent, aEventStatus); 350 } 351 352 void nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext, 353 WidgetGUIEvent* aEvent) { 354 if (mDragging && mOuter) { 355 AdjustChildren(aPresContext); 356 AddListener(); 357 PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed? 358 mDragging = false; 359 State newState = GetState(); 360 // if the state is dragging then make it Open. 361 if (newState == State::Dragging) { 362 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, 363 nsGkAtoms::state, u""_ns, true); 364 } 365 366 mPressed = false; 367 368 // if we dragged then fire a command event. 369 if (mDidDrag) { 370 RefPtr<nsXULElement> element = 371 nsXULElement::FromNode(mOuter->GetContent()); 372 element->DoCommand(); 373 } 374 375 // printf("MouseUp\n"); 376 } 377 378 mChildInfosBefore.Clear(); 379 mChildInfosAfter.Clear(); 380 } 381 382 void nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext, 383 WidgetGUIEvent* aEvent) { 384 if (!mDragging || !mOuter) { 385 return; 386 } 387 388 const bool isHorizontal = !mOuter->IsHorizontal(); 389 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo( 390 aEvent, RelativeTo{mParentBox}); 391 nscoord pos = isHorizontal ? pt.x : pt.y; 392 393 // take our current position and subtract the start location, 394 // mDragStart is in parent-box relative coordinates already. 395 pos -= mDragStart; 396 397 for (auto& info : mChildInfosBefore) { 398 info.changed = info.current; 399 } 400 401 for (auto& info : mChildInfosAfter) { 402 info.changed = info.current; 403 } 404 nscoord oldPos = pos; 405 406 ResizeChildTo(pos); 407 408 State currentState = GetState(); 409 bool supportsBefore = SupportsCollapseDirection(Before); 410 bool supportsAfter = SupportsCollapseDirection(After); 411 412 const bool isRTL = 413 mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl; 414 bool pastEnd = oldPos > 0 && oldPos > pos; 415 bool pastBegin = oldPos < 0 && oldPos < pos; 416 if (isRTL) { 417 // Swap the boundary checks in RTL mode 418 std::swap(pastEnd, pastBegin); 419 } 420 const bool isCollapsedBefore = pastBegin && supportsBefore; 421 const bool isCollapsedAfter = pastEnd && supportsAfter; 422 423 // if we are in a collapsed position 424 if (isCollapsedBefore || isCollapsedAfter) { 425 // and we are not collapsed then collapse 426 if (currentState == State::Dragging) { 427 if (pastEnd) { 428 // printf("Collapse right\n"); 429 if (supportsAfter) { 430 RefPtr<Element> outer = mOuter->mContent->AsElement(); 431 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns, 432 true); 433 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns, 434 true); 435 } 436 437 } else if (pastBegin) { 438 // printf("Collapse left\n"); 439 if (supportsBefore) { 440 RefPtr<Element> outer = mOuter->mContent->AsElement(); 441 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"before"_ns, 442 true); 443 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns, 444 true); 445 } 446 } 447 } 448 } else { 449 // if we are not in a collapsed position and we are not dragging make sure 450 // we are dragging. 451 if (currentState != State::Dragging) { 452 mOuter->mContent->AsElement()->SetAttr( 453 kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true); 454 } 455 AdjustChildren(aPresContext); 456 } 457 458 mDidDrag = true; 459 } 460 461 void nsSplitterFrameInner::AddListener() { 462 mOuter->GetContent()->AddEventListener(u"mouseup"_ns, this, false, false); 463 mOuter->GetContent()->AddEventListener(u"mousedown"_ns, this, false, false); 464 mOuter->GetContent()->AddEventListener(u"mousemove"_ns, this, false, false); 465 mOuter->GetContent()->AddEventListener(u"mouseout"_ns, this, false, false); 466 } 467 468 void nsSplitterFrameInner::RemoveListener() { 469 NS_ENSURE_TRUE_VOID(mOuter); 470 mOuter->GetContent()->RemoveEventListener(u"mouseup"_ns, this, false); 471 mOuter->GetContent()->RemoveEventListener(u"mousedown"_ns, this, false); 472 mOuter->GetContent()->RemoveEventListener(u"mousemove"_ns, this, false); 473 mOuter->GetContent()->RemoveEventListener(u"mouseout"_ns, this, false); 474 } 475 476 nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) { 477 nsAutoString eventType; 478 aEvent->GetType(eventType); 479 if (eventType.EqualsLiteral("mouseup")) { 480 return MouseUp(aEvent); 481 } 482 if (eventType.EqualsLiteral("mousedown")) { 483 return MouseDown(aEvent); 484 } 485 if (eventType.EqualsLiteral("mousemove") || 486 eventType.EqualsLiteral("mouseout")) { 487 return MouseMove(aEvent); 488 } 489 490 MOZ_ASSERT_UNREACHABLE("Unexpected eventType"); 491 return NS_OK; 492 } 493 494 nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) { 495 NS_ENSURE_TRUE(mOuter, NS_OK); 496 mPressed = false; 497 498 PresShell::ReleaseCapturingContent(); 499 500 return NS_OK; 501 } 502 503 template <typename LengthLike> 504 static nscoord ToLengthWithFallback(const LengthLike& aLengthLike, 505 nscoord aFallback) { 506 if (aLengthLike.ConvertsToLength()) { 507 return aLengthLike.ToLength(); 508 } 509 return aFallback; 510 } 511 512 template <typename LengthLike> 513 static nsSize ToLengthWithFallback(const LengthLike& aWidth, 514 const LengthLike& aHeight, 515 nscoord aFallback = 0) { 516 return {ToLengthWithFallback(aWidth, aFallback), 517 ToLengthWithFallback(aHeight, aFallback)}; 518 } 519 520 static void ApplyMargin(nsSize& aSize, const nsMargin& aMargin) { 521 if (aSize.width != NS_UNCONSTRAINEDSIZE) { 522 aSize.width += aMargin.LeftRight(); 523 } 524 if (aSize.height != NS_UNCONSTRAINEDSIZE) { 525 aSize.height += aMargin.TopBottom(); 526 } 527 } 528 529 nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) { 530 NS_ENSURE_TRUE(mOuter, NS_OK); 531 dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent(); 532 if (!mouseEvent) { 533 return NS_OK; 534 } 535 536 // only if left button 537 if (mouseEvent->Button() != 0) { 538 return NS_OK; 539 } 540 541 if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, 542 nsGkAtoms::_true, eCaseMatters)) { 543 return NS_OK; 544 } 545 546 mParentBox = GetValidParentBox(mOuter); 547 if (!mParentBox) { 548 return NS_OK; 549 } 550 551 // get our index 552 mDidDrag = false; 553 554 EnsureOrient(); 555 const bool isHorizontal = !mOuter->IsHorizontal(); 556 557 const nsIContent* outerContent = mOuter->GetContent(); 558 559 const ResizeType resizeBefore = GetResizeBefore(); 560 const ResizeType resizeAfter = GetResizeAfter(); 561 const int32_t childCount = mParentBox->PrincipalChildList().GetLength(); 562 563 mChildInfosBefore.Clear(); 564 mChildInfosAfter.Clear(); 565 int32_t count = 0; 566 567 bool foundOuter = false; 568 CSSOrderAwareFrameIterator iter( 569 mParentBox, FrameChildListID::Principal, 570 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, 571 CSSOrderAwareFrameIterator::OrderState::Unknown, 572 CSSOrderAwareFrameIterator::OrderingProperty::Order); 573 for (; !iter.AtEnd(); iter.Next()) { 574 nsIFrame* childBox = iter.get(); 575 if (childBox == mOuter) { 576 foundOuter = true; 577 if (!count) { 578 // We're at the beginning, nothing to do. 579 return NS_OK; 580 } 581 if (count == childCount - 1 && resizeAfter != ResizeType::Grow) { 582 // If it's the last index then we need to allow for resizeafter="grow" 583 return NS_OK; 584 } 585 } 586 count++; 587 588 nsIContent* content = childBox->GetContent(); 589 // XXX flex seems untested, as it uses mBoxFlex rather than actual flexbox 590 // flex. 591 const nscoord flex = childBox->StyleXUL()->mBoxFlex; 592 const bool isBefore = !foundOuter; 593 const bool isResizable = [&] { 594 if (auto* element = nsXULElement::FromNode(content)) { 595 if (element->NodeInfo()->NameAtom() == nsGkAtoms::splitter) { 596 // skip over any splitters 597 return false; 598 } 599 600 // We need to check for hidden attribute too, since treecols with 601 // the hidden attribute are not really hidden, just collapsed 602 if (element->GetXULBoolAttr(nsGkAtoms::fixed) || 603 element->GetBoolAttr(nsGkAtoms::hidden)) { 604 return false; 605 } 606 } 607 608 // We need to check this here rather than in the switch before because we 609 // want `sibling` to work in the DOM order, not frame tree order. 610 if (resizeBefore == ResizeType::Sibling && 611 content->GetNextElementSibling() == outerContent) { 612 return true; 613 } 614 if (resizeAfter == ResizeType::Sibling && 615 content->GetPreviousElementSibling() == outerContent) { 616 return true; 617 } 618 619 const ResizeType resizeType = isBefore ? resizeBefore : resizeAfter; 620 switch (resizeType) { 621 case ResizeType::Grow: 622 case ResizeType::None: 623 case ResizeType::Sibling: 624 return false; 625 case ResizeType::Flex: 626 return flex > 0; 627 case ResizeType::Closest: 628 case ResizeType::Farthest: 629 break; 630 } 631 return true; 632 }(); 633 634 if (!isResizable) { 635 continue; 636 } 637 638 nsSize curSize = childBox->GetSize(); 639 const auto& pos = *childBox->StylePosition(); 640 const auto anchorResolutionParams = 641 AnchorPosResolutionParams::From(childBox); 642 nsSize minSize = 643 ToLengthWithFallback(*pos.GetMinWidth(anchorResolutionParams), 644 *pos.GetMinHeight(anchorResolutionParams)); 645 nsSize maxSize = ToLengthWithFallback( 646 *pos.GetMaxWidth(anchorResolutionParams), 647 *pos.GetMaxHeight(anchorResolutionParams), NS_UNCONSTRAINEDSIZE); 648 nsSize prefSize(ToLengthWithFallback(*pos.GetWidth(anchorResolutionParams), 649 curSize.width), 650 ToLengthWithFallback(*pos.GetHeight(anchorResolutionParams), 651 curSize.height)); 652 653 maxSize.width = std::max(maxSize.width, minSize.width); 654 maxSize.height = std::max(maxSize.height, minSize.height); 655 prefSize.width = CSSMinMax(prefSize.width, minSize.width, maxSize.width); 656 prefSize.height = 657 CSSMinMax(prefSize.height, minSize.height, maxSize.height); 658 659 nsMargin m; 660 childBox->StyleMargin()->GetMargin(m); 661 662 ApplyMargin(curSize, m); 663 ApplyMargin(minSize, m); 664 ApplyMargin(maxSize, m); 665 ApplyMargin(prefSize, m); 666 667 auto& list = isBefore ? mChildInfosBefore : mChildInfosAfter; 668 nsSplitterInfo& info = *list.AppendElement(); 669 info.childElem = content; 670 info.min = isHorizontal ? minSize.width : minSize.height; 671 info.max = isHorizontal ? maxSize.width : maxSize.height; 672 info.pref = isHorizontal ? prefSize.width : prefSize.height; 673 info.current = info.changed = isHorizontal ? curSize.width : curSize.height; 674 } 675 676 if (!foundOuter) { 677 return NS_OK; 678 } 679 680 mPressed = true; 681 682 const bool reverseDirection = [&] { 683 MOZ_ASSERT(mParentBox->IsFlexContainerFrame()); 684 const FlexboxAxisInfo info(mParentBox); 685 if (!info.mIsRowOriented) { 686 return info.mIsMainAxisReversed; 687 } 688 const bool rtl = 689 mParentBox->StyleVisibility()->mDirection == StyleDirection::Rtl; 690 return info.mIsMainAxisReversed != rtl; 691 }(); 692 693 if (reverseDirection) { 694 // The before array is really the after array, and the order needs to be 695 // reversed. First reverse both arrays. 696 mChildInfosBefore.Reverse(); 697 mChildInfosAfter.Reverse(); 698 699 // Now swap the two arrays. 700 std::swap(mChildInfosBefore, mChildInfosAfter); 701 } 702 703 // if resizebefore is not Farthest, reverse the list because the first child 704 // in the list is the farthest, and we want the first child to be the closest. 705 if (resizeBefore != ResizeType::Farthest) { 706 mChildInfosBefore.Reverse(); 707 } 708 709 // if the resizeafter is the Farthest we must reverse the list because the 710 // first child in the list is the closest we want the first child to be the 711 // Farthest. 712 if (resizeAfter == ResizeType::Farthest) { 713 mChildInfosAfter.Reverse(); 714 } 715 716 int32_t c; 717 nsPoint pt = 718 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox); 719 if (isHorizontal) { 720 c = pt.x; 721 mSplitterPos = mOuter->mRect.x; 722 } else { 723 c = pt.y; 724 mSplitterPos = mOuter->mRect.y; 725 } 726 727 mDragStart = c; 728 729 // printf("Pressed mDragStart=%d\n",mDragStart); 730 731 PresShell::SetCapturingContent(mOuter->GetContent(), 732 CaptureFlags::IgnoreAllowedState); 733 734 return NS_OK; 735 } 736 737 nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) { 738 NS_ENSURE_TRUE(mOuter, NS_OK); 739 if (!mPressed) { 740 return NS_OK; 741 } 742 743 if (mDragging) { 744 return NS_OK; 745 } 746 747 nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this); 748 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state, 749 u"dragging"_ns, true); 750 751 RemoveListener(); 752 mDragging = true; 753 754 return NS_OK; 755 } 756 757 bool nsSplitterFrameInner::SupportsCollapseDirection( 758 nsSplitterFrameInner::CollapseDirection aDirection) { 759 static Element::AttrValuesArray strings[] = { 760 nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr}; 761 762 switch (SplitterElement()->FindAttrValueIn( 763 kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) { 764 case 0: 765 return (aDirection == Before); 766 case 1: 767 return (aDirection == After); 768 case 2: 769 return true; 770 } 771 772 return false; 773 } 774 775 static nsIFrame* SlowOrderAwareSibling(nsIFrame* aBox, bool aNext) { 776 nsIFrame* parent = aBox->GetParent(); 777 if (!parent) { 778 return nullptr; 779 } 780 CSSOrderAwareFrameIterator iter( 781 parent, FrameChildListID::Principal, 782 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, 783 CSSOrderAwareFrameIterator::OrderState::Unknown, 784 CSSOrderAwareFrameIterator::OrderingProperty::Order); 785 786 nsIFrame* prevSibling = nullptr; 787 for (; !iter.AtEnd(); iter.Next()) { 788 nsIFrame* current = iter.get(); 789 if (!aNext && current == aBox) { 790 return prevSibling; 791 } 792 if (aNext && prevSibling == aBox) { 793 return current; 794 } 795 prevSibling = current; 796 } 797 return nullptr; 798 } 799 800 void nsSplitterFrameInner::UpdateState() { 801 // State Transitions: 802 // Open -> Dragging 803 // Open -> CollapsedBefore 804 // Open -> CollapsedAfter 805 // CollapsedBefore -> Open 806 // CollapsedBefore -> Dragging 807 // CollapsedAfter -> Open 808 // CollapsedAfter -> Dragging 809 // Dragging -> Open 810 // Dragging -> CollapsedBefore (auto collapse) 811 // Dragging -> CollapsedAfter (auto collapse) 812 813 State newState = GetState(); 814 815 if (newState == mState) { 816 // No change. 817 return; 818 } 819 820 if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) && 821 IsValidParentBox(mOuter->GetParent())) { 822 // Find the splitter's immediate sibling. 823 const bool prev = 824 newState == State::CollapsedBefore || mState == State::CollapsedBefore; 825 nsIFrame* splitterSibling = SlowOrderAwareSibling(mOuter, !prev); 826 if (splitterSibling) { 827 nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent(); 828 if (sibling && sibling->IsElement()) { 829 if (mState == State::CollapsedBefore || 830 mState == State::CollapsedAfter) { 831 // CollapsedBefore -> Open 832 // CollapsedBefore -> Dragging 833 // CollapsedAfter -> Open 834 // CollapsedAfter -> Dragging 835 nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable( 836 sibling->AsElement(), nsGkAtoms::collapsed)); 837 } else if ((mState == State::Open || mState == State::Dragging) && 838 (newState == State::CollapsedBefore || 839 newState == State::CollapsedAfter)) { 840 // Open -> CollapsedBefore / CollapsedAfter 841 // Dragging -> CollapsedBefore / CollapsedAfter 842 nsContentUtils::AddScriptRunner(new nsSetAttrRunnable( 843 sibling->AsElement(), nsGkAtoms::collapsed, u"true"_ns)); 844 } 845 } 846 } 847 } 848 mState = newState; 849 } 850 851 void nsSplitterFrameInner::EnsureOrient() { 852 mOuter->mIsHorizontal = SplitterIsHorizontal(mParentBox); 853 } 854 855 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) { 856 EnsureOrient(); 857 const bool isHorizontal = !mOuter->IsHorizontal(); 858 859 AdjustChildren(aPresContext, mChildInfosBefore, isHorizontal); 860 AdjustChildren(aPresContext, mChildInfosAfter, isHorizontal); 861 } 862 863 static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox, 864 nsIContent* aContent) { 865 // XXX Can this use GetPrimaryFrame? 866 for (nsIFrame* f : aParentBox->PrincipalChildList()) { 867 if (f->GetContent() == aContent) { 868 return f; 869 } 870 } 871 return nullptr; 872 } 873 874 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext, 875 nsTArray<nsSplitterInfo>& aChildInfos, 876 bool aIsHorizontal) { 877 /// printf("------- AdjustChildren------\n"); 878 879 for (auto& info : aChildInfos) { 880 nscoord newPref = info.pref + (info.changed - info.current); 881 if (nsIFrame* childBox = 882 GetChildBoxForContent(mParentBox, info.childElem)) { 883 SetPreferredSize(childBox, aIsHorizontal, newPref); 884 } 885 } 886 } 887 888 void nsSplitterFrameInner::SetPreferredSize(nsIFrame* aChildBox, 889 bool aIsHorizontal, nscoord aSize) { 890 nsMargin margin; 891 aChildBox->StyleMargin()->GetMargin(margin); 892 if (aIsHorizontal) { 893 aSize -= (margin.left + margin.right); 894 } else { 895 aSize -= (margin.top + margin.bottom); 896 } 897 898 RefPtr element = nsStyledElement::FromNode(aChildBox->GetContent()); 899 if (!element) { 900 return; 901 } 902 903 // We set both the attribute and the CSS value, so that XUL persist="" keeps 904 // working, see bug 1790712. 905 906 int32_t pixels = aSize / AppUnitsPerCSSPixel(); 907 nsAutoString attrValue; 908 attrValue.AppendInt(pixels); 909 element->SetAttr(aIsHorizontal ? nsGkAtoms::width : nsGkAtoms::height, 910 attrValue, IgnoreErrors()); 911 912 nsCOMPtr<nsDOMCSSDeclaration> decl = element->Style(); 913 914 nsAutoCString cssValue; 915 cssValue.AppendInt(pixels); 916 cssValue.AppendLiteral("px"); 917 decl->SetProperty(aIsHorizontal ? "width"_ns : "height"_ns, cssValue, ""_ns, 918 IgnoreErrors()); 919 } 920 921 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff, 922 nsTArray<nsSplitterInfo>& aChildInfos, 923 int32_t& aSpaceLeft) { 924 aSpaceLeft = 0; 925 926 for (auto& info : aChildInfos) { 927 nscoord min = info.min; 928 nscoord max = info.max; 929 nscoord& c = info.changed; 930 931 // figure our how much space to add or remove 932 if (c + aDiff < min) { 933 aDiff += (c - min); 934 c = min; 935 } else if (c + aDiff > max) { 936 aDiff -= (max - c); 937 c = max; 938 } else { 939 c += aDiff; 940 aDiff = 0; 941 } 942 943 // there is not space left? We are done 944 if (aDiff == 0) { 945 break; 946 } 947 } 948 949 aSpaceLeft = aDiff; 950 } 951 952 /** 953 * Ok if we want to resize a child we will know the actual size in pixels we 954 * want it to be. This is not the preferred size. But the only way we can change 955 * a child is by manipulating its preferred size. So give the actual pixel size 956 * this method will figure out the preferred size and set it. 957 */ 958 959 void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff) { 960 nscoord spaceLeft = 0; 961 962 if (!mChildInfosBefore.IsEmpty()) { 963 AddRemoveSpace(aDiff, mChildInfosBefore, spaceLeft); 964 // If there is any space left over remove it from the diff we were 965 // originally given. 966 aDiff -= spaceLeft; 967 } 968 969 AddRemoveSpace(-aDiff, mChildInfosAfter, spaceLeft); 970 971 if (spaceLeft != 0 && !mChildInfosAfter.IsEmpty()) { 972 aDiff += spaceLeft; 973 AddRemoveSpace(spaceLeft, mChildInfosBefore, spaceLeft); 974 } 975 }