office-gobmx/vcl/osx/salnativewidgets.cxx
Patrick Luby c585b697b5 Related: tdf#163945 don't directly flush graphics with Skia/Metal
When dragging a selection box on an empty background in
Impress and only with Skia/Metal, the selection box
would not keep up with the pointer. The selection box
would repaint sporadically or not at all if the pointer
was dragged rapidly and the status bar was visible.

Apparently, flushing a graphics doesn't actually do much
of anything with Skia/Raster and Skia disabled so the
selection box repaints without any noticeable delay.
However, with Skia/Metal every flush of a graphics
creates and queues a new CAMetalLayer drawable. During
rapid dragging, this can lead to creating and queueing
up to 200 drawables per second leaving no spare time for
the Impress selection box painting timer to fire.

So with Skia/Metal, throttle the rate of flushing by
calling display on the view.

Also, with the reduced Skia/Metal flushing load from this
fix, the color conversion when drawing to an NSBox appears
to be no longer needed. The converted color appeared less
saturated, at least to me, so accept the (hopefully now
imperceptible) performance hit of drawing an NSBox with
native colors.

Change-Id: I55b4ab763bf20c6c2acad65587b703fc6f645264
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/177530
Reviewed-by: Patrick Luby <guibomacdev@gmail.com>
Tested-by: Jenkins
2024-11-29 16:15:04 +01:00

1394 lines
60 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <config_features.h>
#include <tools/long.hxx>
#include <vcl/salnativewidgets.hxx>
#include <vcl/decoview.hxx>
#include <vcl/svapp.hxx>
#include <vcl/threadex.hxx>
#include <vcl/timer.hxx>
#include <vcl/settings.hxx>
#include <vcl/themecolors.hxx>
#include <quartz/salgdi.h>
#include <osx/salnativewidgets.h>
#include <osx/saldata.hxx>
#include <osx/salframe.h>
#include <premac.h>
#include <Carbon/Carbon.h>
#include <postmac.h>
#include "cuidraw.hxx"
// presentation of native widgets consists of two important methods:
// AquaSalGraphics::getNativeControlRegion to determine native rectangle in pixels to draw the widget
// AquaSalGraphics::drawNativeControl to do the drawing operation itself
// getNativeControlRegion has to calculate a content rectangle within it is safe to draw the widget. Furthermore a bounding rectangle
// has to be calculated by getNativeControlRegion to consider adornments like a focus rectangle. As drawNativeControl uses Carbon
// API calls, all widgets are drawn without text. Drawing of text is done separately by VCL on top of graphical Carbon widget
// representation. drawNativeControl is called by VCL using content rectangle determined by getNativeControlRegion.
// FIXME: when calculation bounding rectangle larger then content rectangle, text displayed by VCL will become misaligned. To avoid
// misalignment bounding rectangle and content rectangle are calculated equally including adornments. Reduction of size for content
// is done by drawNativeControl subsequently. Only exception is editbox: As other widgets have distinct ControlPart::SubEdit control
// parts, editbox bounding rectangle and content rectangle are both calculated to reflect content area. Extending size for
// adornments is done by drawNativeControl subsequently.
#if !HAVE_FEATURE_MACOSX_SANDBOX
@interface NSWindow(CoreUIRendererPrivate)
+ (CUIRendererRef)coreUIRenderer;
@end
#endif
static HIRect ImplGetHIRectFromRectangle(tools::Rectangle aRect)
{
HIRect aHIRect;
aHIRect.origin.x = static_cast<float>(aRect.Left());
aHIRect.origin.y = static_cast<float>(aRect.Top());
aHIRect.size.width = static_cast<float>(aRect.GetWidth());
aHIRect.size.height = static_cast<float>(aRect.GetHeight());
return aHIRect;
}
static NSControlStateValue ImplGetButtonValue(ButtonValue aButtonValue)
{
switch (aButtonValue)
{
case ButtonValue::On:
return NSControlStateValueOn;
case ButtonValue::Off:
case ButtonValue::DontKnow:
return NSControlStateValueOff;
case ButtonValue::Mixed:
default:
return NSControlStateValueMixed;
}
}
static bool AquaGetScrollRect(/* TODO: int nScreen, */
ControlPart nPart, const tools::Rectangle &rControlRect, tools::Rectangle &rResultRect)
{
bool bRetVal = true;
rResultRect = rControlRect;
switch (nPart)
{
case ControlPart::ButtonUp:
rResultRect.SetBottom(rResultRect.Top());
break;
case ControlPart::ButtonDown:
rResultRect.SetTop(rResultRect.Bottom());
break;
case ControlPart::ButtonLeft:
rResultRect.SetRight(rResultRect.Left());
break;
case ControlPart::ButtonRight:
rResultRect.SetLeft(rResultRect.Right());
break;
case ControlPart::TrackHorzArea:
case ControlPart::TrackVertArea:
case ControlPart::ThumbHorz:
case ControlPart::ThumbVert:
case ControlPart::TrackHorzLeft:
case ControlPart::TrackHorzRight:
case ControlPart::TrackVertUpper:
case ControlPart::TrackVertLower:
break;
default:
bRetVal = false;
}
return bRetVal;
}
bool AquaSalGraphics::isNativeControlSupported(ControlType nType, ControlPart nPart)
{
// native controls are now defaults. If you want to disable native controls, set the environment variable SAL_NO_NWF to
// something and VCL controls will be used as default again.
switch (nType)
{
case ControlType::Pushbutton:
case ControlType::Radiobutton:
case ControlType::Checkbox:
case ControlType::ListNode:
if (nPart == ControlPart::Entire)
return true;
break;
case ControlType::Scrollbar:
if (nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert
|| nPart == ControlPart::Entire || nPart == ControlPart::HasThreeButtons)
return true;
break;
case ControlType::Slider:
if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
return true;
break;
case ControlType::Editbox:
if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
return true;
break;
case ControlType::MultilineEditbox:
if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
return true;
break;
case ControlType::Spinbox:
if (nPart == ControlPart::Entire || nPart == ControlPart::AllButtons || nPart == ControlPart::HasBackgroundTexture)
return true;
break;
case ControlType::SpinButtons:
return false;
case ControlType::Combobox:
if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
return true;
break;
case ControlType::Listbox:
if (nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow || nPart == ControlPart::HasBackgroundTexture
|| nPart == ControlPart::SubEdit)
return true;
break;
case ControlType::TabItem:
case ControlType::TabPane:
case ControlType::TabBody:
if (nPart == ControlPart::Entire || nPart == ControlPart::TabsDrawRtl || nPart == ControlPart::HasBackgroundTexture)
return true;
break;
case ControlType::Toolbar:
if (nPart == ControlPart::Entire || nPart == ControlPart::DrawBackgroundHorz
|| nPart == ControlPart::DrawBackgroundVert)
return true;
break;
case ControlType::WindowBackground:
if (nPart == ControlPart::BackgroundWindow || nPart == ControlPart::BackgroundDialog)
return true;
break;
case ControlType::Menubar:
if (nPart == ControlPart::Entire)
return true;
break;
case ControlType::Tooltip:
if (nPart == ControlPart::Entire)
return true;
break;
case ControlType::MenuPopup:
if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::MenuItemCheckMark
|| nPart == ControlPart::MenuItemRadioMark)
return true;
break;
case ControlType::LevelBar:
case ControlType::Progress:
case ControlType::IntroProgress:
if (nPart == ControlPart::Entire)
return true;
break;
case ControlType::Frame:
if (nPart == ControlPart::Border)
return true;
break;
case ControlType::ListNet:
if (nPart == ControlPart::Entire)
return true;
break;
default:
break;
}
return false;
}
bool AquaSalGraphics::hitTestNativeControl(ControlType nType, ControlPart nPart, const tools::Rectangle &rControlRegion,
const Point &rPos, bool& rIsInside)
{
if (nType == ControlType::Scrollbar)
{
tools::Rectangle aRect;
bool bValid = AquaGetScrollRect(/* TODO: int nScreen, */
nPart, rControlRegion, aRect);
rIsInside = bValid && aRect.Contains(rPos);
return bValid;
}
return false;
}
static bool getEnabled(ControlState nState, AquaSalFrame* mpFrame)
{
// there are non key windows which are children of key windows, e.g. autofilter configuration dialog or sidebar dropdown dialogs.
// To handle these windows correctly, parent frame's key window state is considered here additionally.
const bool bDrawActive = mpFrame == nullptr || [mpFrame->getNSWindow() isKeyWindow]
|| mpFrame->mpParent == nullptr || [mpFrame->mpParent->getNSWindow() isKeyWindow];
if (!(nState & ControlState::ENABLED) || !bDrawActive)
{
return false;
}
return true;
}
bool AquaSalGraphics::drawNativeControl(ControlType nType,
ControlPart nPart,
const tools::Rectangle &rControlRegion,
ControlState nState,
const ImplControlValue &aValue,
const OUString &,
const Color&)
{
return mpBackend->drawNativeControl(nType, nPart, rControlRegion, nState, aValue);
}
static NSColor* colorFromRGB(const Color& rColor)
{
return [NSColor colorWithSRGBRed:(rColor.GetRed() / 255.0f)
green:(rColor.GetGreen() / 255.0f)
blue:(rColor.GetBlue() / 255.0f)
alpha:(rColor.GetAlpha() / 255.0f)];
}
static void paintCell(NSCell* pBtn, const NSRect& bounds, bool bShowsFirstResponder, CGContextRef context, NSView* pView)
{
//translate and scale because up side down otherwise
CGContextSaveGState(context);
CGContextTranslateCTM(context, bounds.origin.x, bounds.origin.y + bounds.size.height);
CGContextScaleCTM(context, 1, -1);
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];
NSRect rect = { NSZeroPoint, bounds.size };
if ([pBtn isKindOfClass: [NSSliderCell class]])
{
// NSSliderCell doesn't seem to work with drawWithFrame(?), so draw the elements directly
[static_cast<NSSliderCell*>(pBtn)
drawBarInside: [static_cast<NSSliderCell*>(pBtn) barRectFlipped: NO] flipped: NO];
rect = [static_cast<NSSliderCell*>(pBtn) knobRectFlipped: NO];
[static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
}
else
[pBtn drawWithFrame: rect inView: pView];
// setShowsFirstResponder apparently causes a hang when set on NSComboBoxCell
const bool bIsComboBox = [pBtn isMemberOfClass: [NSComboBoxCell class]];
if (!bIsComboBox)
[pBtn setShowsFirstResponder: bShowsFirstResponder];
if (bShowsFirstResponder)
{
NSSetFocusRingStyle(NSFocusRingOnly);
CGContextBeginTransparencyLayerWithRect(context, rect, nullptr);
if ([pBtn isMemberOfClass: [NSTextFieldCell class]])
{
// I wonder why NSTextFieldCell doesn't work for me in the default else branch.
// NSComboBoxCell works, and that derives from NSTextFieldCell, on the other
// hand setShowsFirstResponder causes a hangs when set on NSComboBoxCell
NSRect out = [pBtn focusRingMaskBoundsForFrame: rect inView: pView];
CGContextFillRect(context, out);
}
else if ([pBtn isKindOfClass: [NSSliderCell class]])
{
// Not getting anything useful for a NSSliderCell, so use the knob
[static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
}
else
[pBtn drawFocusRingMaskWithFrame:rect inView: pView];
CGContextEndTransparencyLayer(context);
}
[NSGraphicsContext setCurrentContext:savedContext];
CGContextRestoreGState(context);
}
static void paintFocusRect(double radius, const NSRect& rect, CGContextRef context)
{
NSRect bounds = rect;
CGPathRef path = CGPathCreateWithRoundedRect(bounds, radius, radius, nullptr);
CGContextSetStrokeColorWithColor(context, [NSColor keyboardFocusIndicatorColor].CGColor);
CGContextSetLineWidth(context, FOCUS_RING_WIDTH);
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CFRelease(path);
}
@interface FixedWidthTabViewItem : NSTabViewItem {
int m_nWidth;
}
- (NSSize)sizeOfLabel: (BOOL)computeMin;
- (void)setTabWidth: (int)nWidth;
@end
@implementation FixedWidthTabViewItem
- (NSSize)sizeOfLabel: (BOOL)computeMin
{
NSSize size = [super sizeOfLabel: computeMin];
size.width = m_nWidth;
return size;
}
- (void)setTabWidth: (int)nWidth
{
m_nWidth = nWidth;
}
@end
bool AquaGraphicsBackend::drawNativeControl(ControlType nType,
ControlPart nPart,
const tools::Rectangle &rControlRegion,
ControlState nState,
const ImplControlValue &aValue)
{
if (!mrShared.checkContext())
return false;
mrShared.maContextHolder.saveState();
bool bOK = performDrawNativeControl(nType, nPart, rControlRegion, nState, aValue,
mrShared.maContextHolder.get(), mrShared.mpFrame);
mrShared.maContextHolder.restoreState();
tools::Rectangle buttonRect = rControlRegion;
// in most cases invalidating the whole control region instead of just the unclipped part of it is sufficient (and probably
// faster). However for the window background we should not unnecessarily enlarge the really changed rectangle since the
// difference is usually quite high. Background is always drawn as a whole since we don't know anything about its possible
// contents (see issue i90291).
if (nType == ControlType::WindowBackground)
{
CGRect aRect = {{0, 0}, {0, 0}};
if (mrShared.mxClipPath)
aRect = CGPathGetBoundingBox(mrShared.mxClipPath);
if (aRect.size.width != 0 && aRect.size.height != 0)
buttonRect.Intersection(tools::Rectangle(Point(static_cast<tools::Long>(aRect.origin.x),
static_cast<tools::Long>(aRect.origin.y)),
Size(static_cast<tools::Long>(aRect.size.width),
static_cast<tools::Long>(aRect.size.height))));
}
mrShared.refreshRect(buttonRect.Left(), buttonRect.Top(), buttonRect.GetWidth(), buttonRect.GetHeight());
return bOK;
}
static void drawBox(CGContextRef context, const NSRect& rc, NSColor* pColor)
{
assert(pColor);
CGContextSaveGState(context);
CGContextTranslateCTM(context, rc.origin.x, rc.origin.y + rc.size.height);
CGContextScaleCTM(context, 1, -1);
NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
NSBox* pBox = [[NSBox alloc] initWithFrame: rect];
[pBox setBoxType: NSBoxCustom];
[pBox setFillColor: pColor];
// -[NSBox setBorderType: NSNoBorder] is deprecated so hide the border
// by setting the border color to transparent
[pBox setBorderColor: [NSColor clearColor]];
[pBox setTitlePosition: NSNoTitle];
[pBox displayRectIgnoringOpacity: rect inContext: graphicsContext];
[pBox release];
CGContextRestoreGState(context);
}
// if I don't crystallize this bg then the InvertCursor using kCGBlendModeDifference doesn't
// work correctly and the cursor doesn't appear correctly
static void drawEditableBackground(CGContextRef context, const NSRect& rc)
{
CGContextSaveGState(context);
if (ThemeColors::IsThemeLoaded())
CGContextSetFillColorWithColor(context, colorFromRGB(ThemeColors::GetThemeColors().GetBaseColor()).CGColor);
else
CGContextSetFillColorWithColor(context, [NSColor controlBackgroundColor].CGColor);
CGContextFillRect(context, rc);
CGContextRestoreGState(context);
}
// As seen in macOS 12.3.1. All a bit odd really.
const int RoundedMargin[4] = { 6, 4, 0, 3 };
bool AquaGraphicsBackendBase::performDrawNativeControl(ControlType nType,
ControlPart nPart,
const tools::Rectangle &rControlRegion,
ControlState nState,
const ImplControlValue &aValue,
CGContextRef context,
AquaSalFrame* mpFrame)
{
bool bOK = false;
bool bThemeLoaded(ThemeColors::IsThemeLoaded());
AquaSalInstance* pInst = GetSalData()->mpInstance;
HIRect rc = ImplGetHIRectFromRectangle(rControlRegion);
switch (nType)
{
case ControlType::Toolbar:
{
if (bThemeLoaded)
drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetWindowColor()));
else
drawBox(context, rc, NSColor.windowBackgroundColor);
bOK = true;
}
break;
case ControlType::WindowBackground:
{
if (bThemeLoaded)
drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetWindowColor()));
else
drawBox(context, rc, NSColor.windowBackgroundColor);
bOK = true;
}
break;
case ControlType::Tooltip:
{
rc.size.width += 2;
rc.size.height += 2;
if (bThemeLoaded)
drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetBaseColor()));
else
drawBox(context, rc, NSColor.controlBackgroundColor);
bOK = true;
}
break;
case ControlType::Menubar:
case ControlType::MenuPopup:
if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::HasBackgroundTexture)
{
// FIXME: without this magical offset there is a 2 pixel black border on the right
rc.size.width += 2;
HIThemeMenuDrawInfo aMenuInfo;
aMenuInfo.version = 0;
aMenuInfo.menuType = kThemeMenuTypePullDown;
HIThemeMenuItemDrawInfo aMenuItemDrawInfo;
// grey theme when the item is selected is drawn here.
aMenuItemDrawInfo.itemType = kThemeMenuItemPlain;
if ((nPart == ControlPart::MenuItem) && (nState & ControlState::SELECTED))
// blue theme when the item is selected is drawn here.
aMenuItemDrawInfo.state = kThemeMenuSelected;
else
// normal color for non selected item
aMenuItemDrawInfo.state = kThemeMenuActive;
// repaints the background of the pull down menu
HIThemeDrawMenuBackground(&rc, &aMenuInfo, context, kHIThemeOrientationNormal);
// repaints the item either blue (selected) and/or grey (active only)
HIThemeDrawMenuItem(&rc, &rc, &aMenuItemDrawInfo, context, kHIThemeOrientationNormal, &rc);
bOK = true;
}
else if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
{
// checked, else it is not displayed (see vcl/source/window/menu.cxx)
if (nState & ControlState::PRESSED)
{
HIThemeTextInfo aTextInfo;
aTextInfo.version = 0;
aTextInfo.state = (nState & ControlState::ENABLED) ? kThemeStateInactive: kThemeStateActive;
aTextInfo.fontID = kThemeMenuItemMarkFont;
aTextInfo.horizontalFlushness = kHIThemeTextHorizontalFlushCenter;
aTextInfo.verticalFlushness = kHIThemeTextVerticalFlushTop;
aTextInfo.options = kHIThemeTextBoxOptionNone;
aTextInfo.truncationPosition = kHIThemeTextTruncationNone;
// aTextInfo.truncationMaxLines unused because of kHIThemeTextTruncationNone item highlighted
if (nState & ControlState::SELECTED) aTextInfo.state = kThemeStatePressed;
UniChar mark=(nPart == ControlPart::MenuItemCheckMark) ? kCheckUnicode: kBulletUnicode;
CFStringRef cfString = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &mark, 1, kCFAllocatorNull);
HIThemeDrawTextBox(cfString, &rc, &aTextInfo, context, kHIThemeOrientationNormal);
if (cfString)
CFRelease(cfString);
bOK = true;
}
}
break;
case ControlType::Pushbutton:
{
NSControlSize eSizeKind = NSControlSizeRegular;
NSBezelStyle eBezelStyle = NSBezelStyleRounded;
PushButtonValue const *pPBVal = aValue.getType() == ControlType::Pushbutton ?
static_cast<PushButtonValue const *>(&aValue) : nullptr;
SInt32 nPaintHeight = rc.size.height;
if (rc.size.height <= PUSH_BUTTON_NORMAL_HEIGHT)
{
eSizeKind = NSControlSizeMini;
GetThemeMetric(kThemeMetricSmallPushButtonHeight, &nPaintHeight);
}
else if ((pPBVal && pPBVal->mbSingleLine) || rc.size.height < PUSH_BUTTON_NORMAL_HEIGHT * 3 / 2)
{
GetThemeMetric(kThemeMetricPushButtonHeight, &nPaintHeight);
}
else
{
// A simple square bezel style that can scale to any size
eBezelStyle = NSBezelStyleSmallSquare;
}
// translate the origin for controls with fixed paint height so content ends up somewhere sensible
rc.origin.y += (rc.size.height - nPaintHeight + 1) / 2;
rc.size.height = nPaintHeight;
NSButtonCell* pBtn = pInst->mpButtonCell;
pBtn.allowsMixedState = YES;
[pBtn setTitle: @""];
[pBtn setButtonType: NSButtonTypeMomentaryPushIn];
[pBtn setBezelStyle: eBezelStyle];
[pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
[pBtn setEnabled: getEnabled(nState, mpFrame)];
[pBtn setFocusRingType: NSFocusRingTypeExterior];
[pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
[pBtn setControlSize: eSizeKind];
if (nState & ControlState::DEFAULT)
[pBtn setKeyEquivalent: @"\r"];
else
[pBtn setKeyEquivalent: @""];
if (eBezelStyle == NSBezelStyleRounded)
{
int nMargin = RoundedMargin[eSizeKind];
rc.origin.x -= nMargin;
rc.size.width += nMargin * 2;
rc.origin.x += FOCUS_RING_WIDTH / 2;
rc.size.width -= FOCUS_RING_WIDTH;
}
const bool bFocused(nState & ControlState::FOCUSED);
paintCell(pBtn, rc, bFocused, context, nullptr);
bOK = true;
}
break;
case ControlType::Radiobutton:
case ControlType::Checkbox:
{
rc.size.width -= 2 * FOCUS_RING_WIDTH;
rc.size.height = RADIO_BUTTON_SMALL_SIZE;
rc.origin.x += FOCUS_RING_WIDTH;
rc.origin.y += FOCUS_RING_WIDTH;
NSButtonCell* pBtn = nType == ControlType::Checkbox ? pInst->mpCheckCell : pInst->mpRadioCell;
pBtn.allowsMixedState = YES;
[pBtn setTitle: @""];
[pBtn setButtonType: nType == ControlType::Checkbox ? NSButtonTypeSwitch : NSButtonTypeRadio];
[pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
[pBtn setEnabled: getEnabled(nState, mpFrame)];
[pBtn setFocusRingType: NSFocusRingTypeExterior];
[pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
const bool bFocused(nState & ControlState::FOCUSED);
paintCell(pBtn, rc, bFocused, context, nullptr);
bOK = true;
}
break;
case ControlType::ListNode:
{
NSButtonCell* pBtn = pInst->mpListNodeCell;
pBtn.allowsMixedState = YES;
[pBtn setTitle: @""];
[pBtn setButtonType: NSButtonTypeOnOff];
[pBtn setBezelStyle: NSBezelStyleDisclosure];
[pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
[pBtn setEnabled: getEnabled(nState, mpFrame)];
[pBtn setFocusRingType: NSFocusRingTypeExterior];
const bool bFocused(nState & ControlState::FOCUSED);
paintCell(pBtn, rc, bFocused, context, nullptr);
bOK = true;
}
break;
case ControlType::LevelBar:
{
NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
NSLevelIndicator* pBox = [[NSLevelIndicator alloc] initWithFrame:rect];
[pBox setLevelIndicatorStyle: NSLevelIndicatorStyleContinuousCapacity];
[pBox setMinValue: 0];
[pBox setMaxValue: rc.size.width];
[pBox setCriticalValue: rc.size.width * 35.0 / 100.0];
[pBox setWarningValue: rc.size.width * 70.0 / 100.0];
[pBox setDoubleValue: aValue.getNumericVal()];
CGContextSaveGState(context);
CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
[NSGraphicsContext setCurrentContext: graphicsContext];
[pBox drawRect: rect];
[NSGraphicsContext setCurrentContext: savedContext];
CGContextRestoreGState(context);
[pBox release];
bOK = true;
}
break;
case ControlType::Progress:
case ControlType::IntroProgress:
{
NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
NSProgressIndicator* pBox = [[NSProgressIndicator alloc] initWithFrame: rect];
[pBox setControlSize: (rc.size.height > MEDIUM_PROGRESS_INDICATOR_HEIGHT) ?
NSControlSizeRegular : NSControlSizeSmall];
[pBox setMinValue: 0];
[pBox setMaxValue: rc.size.width];
[pBox setDoubleValue: aValue.getNumericVal()];
pBox.usesThreadedAnimation = NO;
[pBox setIndeterminate: NO];
CGContextSaveGState(context);
CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
[NSGraphicsContext setCurrentContext: graphicsContext];
[pBox drawRect: rect];
[NSGraphicsContext setCurrentContext: savedContext];
CGContextRestoreGState(context);
[pBox release];
bOK = true;
}
break;
case ControlType::Slider:
{
const SliderValue *pSliderVal = static_cast<SliderValue const *>(&aValue);
if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
{
NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
NSSlider* pBox = [[NSSlider alloc] initWithFrame: rect];
[pBox setEnabled: getEnabled(nState, mpFrame)];
[pBox setVertical: nPart == ControlPart::TrackVertArea];
[pBox setMinValue: pSliderVal->mnMin];
[pBox setMaxValue: pSliderVal->mnMax];
[pBox setIntegerValue: pSliderVal->mnCur];
[pBox setSliderType: NSSliderTypeLinear];
[pBox setFocusRingType: NSFocusRingTypeExterior];
const bool bFocused(nState & ControlState::FOCUSED);
paintCell(pBox.cell, rc, bFocused, context, mpFrame->getNSView());
[pBox release];
bOK = true;
}
}
break;
case ControlType::Scrollbar:
{
const ScrollbarValue *pScrollbarVal = (aValue.getType() == ControlType::Scrollbar)
? static_cast<const ScrollbarValue *>(&aValue) : nullptr;
if (nPart == ControlPart::DrawBackgroundVert || nPart == ControlPart::DrawBackgroundHorz)
{
if (bThemeLoaded)
drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetBaseColor()));
else
drawBox(context, rc, NSColor.controlBackgroundColor);
NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
NSScroller* pBar = [[NSScroller alloc] initWithFrame: rect];
double range = pScrollbarVal->mnMax - pScrollbarVal->mnVisibleSize - pScrollbarVal->mnMin;
double value = range ? (pScrollbarVal->mnCur - pScrollbarVal->mnMin) / range : 0;
double length = pScrollbarVal->mnMax - pScrollbarVal->mnMin;
double proportion = pScrollbarVal->mnVisibleSize / length;
[pBar setEnabled: getEnabled(nState, mpFrame)];
[pBar setScrollerStyle: NSScrollerStyleLegacy];
[pBar setFloatValue: value];
[pBar setKnobProportion: proportion];
bool bPressed = (pScrollbarVal->mnThumbState & ControlState::ENABLED) &&
(pScrollbarVal->mnThumbState & ControlState::PRESSED);
CGContextSaveGState(context);
CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
[NSGraphicsContext setCurrentContext: graphicsContext];
// For not-pressed first draw without the knob and then
// draw just the knob but with 50% opaque which looks sort of
// right
[pBar drawKnobSlotInRect: rect highlight: NO];
NSBitmapImageRep* pImageRep = [pBar bitmapImageRepForCachingDisplayInRect: rect];
NSGraphicsContext* imageContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:pImageRep];
[NSGraphicsContext setCurrentContext: imageContext];
[pBar drawKnob];
[NSGraphicsContext setCurrentContext: graphicsContext];
NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
[pImage addRepresentation: pImageRep]; // takes ownership of pImageRep
[pImage drawInRect: rect fromRect: rect
operation: NSCompositingOperationSourceOver
fraction: bPressed ? 1.0 : 0.5];
[pImage release];
[NSGraphicsContext setCurrentContext:savedContext];
CGContextRestoreGState(context);
bOK = true;
[pBar release];
}
}
break;
case ControlType::TabPane:
{
NSTabView* pBox = [[NSTabView alloc] initWithFrame: rc];
SInt32 nOverlap;
GetThemeMetric(kThemeMetricTabFrameOverlap, &nOverlap);
// this calculation is probably more than a little dubious
rc.origin.x -= pBox.contentRect.origin.x - FOCUS_RING_WIDTH;
rc.size.width += rc.size.width - pBox.contentRect.size.width - 2 * FOCUS_RING_WIDTH;
double nTopBorder = pBox.contentRect.origin.y;
double nBottomBorder = rc.size.height - pBox.contentRect.size.height - nTopBorder;
double nExtraTop = (nTopBorder - nBottomBorder) / 2;
rc.origin.y -= (nTopBorder - nExtraTop + nOverlap);
rc.size.height += (nTopBorder - nExtraTop + nBottomBorder);
CGContextSaveGState(context);
CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
rc.origin.x = 0;
rc.origin.y = 0;
[pBox setBoundsOrigin: rc.origin];
[pBox setBoundsSize: rc.size];
// jam this in to force the tab contents area to be left undrawn, the ControlType::TabItem
// will be drawn in this space.
const TabPaneValue& rValue = static_cast<const TabPaneValue&>(aValue);
SInt32 nEndCapWidth;
GetThemeMetric(kThemeMetricLargeTabCapsWidth, &nEndCapWidth);
FixedWidthTabViewItem* pItem = [[[FixedWidthTabViewItem alloc] initWithIdentifier: @"tab"] autorelease];
[pItem setTabWidth: rValue.m_aTabHeaderRect.GetWidth() - 2 * nEndCapWidth];
[pBox addTabViewItem: pItem];
NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
[NSGraphicsContext setCurrentContext: graphicsContext];
[pBox drawRect: rc];
[NSGraphicsContext setCurrentContext: savedContext];
[pBox release];
CGContextRestoreGState(context);
bOK = true;
}
break;
case ControlType::TabItem:
{
// first, last or middle tab
TabitemValue const * pTabValue = static_cast<TabitemValue const *>(&aValue);
TabitemFlags nAlignment = pTabValue->mnAlignment;
// TabitemFlags::LeftAligned (and TabitemFlags::RightAligned) for the leftmost (or rightmost) tab
// when there are several lines of tabs because there is only one first tab and one
// last tab and TabitemFlags::FirstInGroup (and TabitemFlags::LastInGroup) because when the
// line width is different from window width, there may not be TabitemFlags::RightAligned
int nPaintIndex = 1;
bool bSolo = false;
if (((nAlignment & TabitemFlags::LeftAligned) && (nAlignment & TabitemFlags::RightAligned))
|| ((nAlignment & TabitemFlags::FirstInGroup) && (nAlignment & TabitemFlags::LastInGroup)))
{
nPaintIndex = 0;
bSolo = true;
}
else if ((nAlignment & TabitemFlags::LeftAligned) || (nAlignment & TabitemFlags::FirstInGroup))
nPaintIndex = !AllSettings::GetLayoutRTL() ? 0 : 2;
else if ((nAlignment & TabitemFlags::RightAligned) || (nAlignment & TabitemFlags::LastInGroup))
nPaintIndex = !AllSettings::GetLayoutRTL() ? 2 : 0;
int nCells = !bSolo ? 3 : 1;
NSRect ctrlrect = { NSZeroPoint, NSMakeSize(rc.size.width * nCells + FOCUS_RING_WIDTH, rc.size.height) };
NSSegmentedControl* pCtrl = [[NSSegmentedControl alloc] initWithFrame: ctrlrect];
[pCtrl setSegmentCount: nCells];
if (bSolo)
[pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH forSegment: 0];
else
{
[pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 0];
[pCtrl setWidth: rc.size.width forSegment: 1];
[pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 2];
}
[pCtrl setSelected: (nState & ControlState::SELECTED) ? YES : NO forSegment: nPaintIndex];
[pCtrl setFocusRingType: NSFocusRingTypeExterior];
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];
NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
NSRect tabrect = { NSMakePoint(rc.size.width * nPaintIndex + FOCUS_RING_WIDTH / 2, 0),
NSMakeSize(rc.size.width, rc.size.height) };
NSBitmapImageRep* pImageRep = [pCtrl bitmapImageRepForCachingDisplayInRect: tabrect];
[pCtrl cacheDisplayInRect: tabrect toBitmapImageRep: pImageRep];
NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
[pImage addRepresentation: pImageRep]; // takes ownership of pImageRep
[pImage drawInRect: rc fromRect: rect
operation: NSCompositingOperationSourceOver
fraction: 1.0];
[pImage release];
[NSGraphicsContext setCurrentContext:savedContext];
[pCtrl release];
if (nState & ControlState::FOCUSED)
{
if (!bSolo)
{
if (nPaintIndex == 0)
{
rc.origin.x += FOCUS_RING_WIDTH / 2;
rc.size.width -= FOCUS_RING_WIDTH / 2;
}
else if (nPaintIndex == 2)
{
rc.size.width -= FOCUS_RING_WIDTH / 2;
rc.size.width -= FOCUS_RING_WIDTH / 2;
}
}
paintFocusRect(4.0, rc, context);
}
bOK=true;
}
break;
case ControlType::Editbox:
case ControlType::MultilineEditbox:
{
rc.size.width += 2 * EDITBOX_INSET_MARGIN;
if (nType == ControlType::Editbox)
rc.size.height = EDITBOX_HEIGHT;
else
rc.size.height += 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
rc.origin.x -= EDITBOX_INSET_MARGIN;
rc.origin.y -= EDITBOX_INSET_MARGIN;
NSTextFieldCell* pBtn = pInst->mpTextFieldCell;
[pBtn setEnabled: getEnabled(nState, mpFrame)];
[pBtn setBezeled: YES];
[pBtn setEditable: YES];
[pBtn setFocusRingType: NSFocusRingTypeExterior];
drawEditableBackground(context, rc);
const bool bFocused(nState & ControlState::FOCUSED);
paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());
bOK = true;
}
break;
case ControlType::Combobox:
if (nPart == ControlPart::HasBackgroundTexture || nPart == ControlPart::Entire)
{
rc.origin.y += (rc.size.height - COMBOBOX_HEIGHT + 1) / 2;
rc.size.height = COMBOBOX_HEIGHT;
NSComboBoxCell* pBtn = pInst->mpComboBoxCell;
[pBtn setEnabled: getEnabled(nState, mpFrame)];
[pBtn setEditable: YES];
[pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
[pBtn setFocusRingType: NSFocusRingTypeExterior];
{
rc.origin.x += 2;
rc.size.width -= 1;
}
drawEditableBackground(context, rc);
const bool bFocused(nState & ControlState::FOCUSED);
paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());
bOK = true;
}
break;
case ControlType::Listbox:
switch (nPart)
{
case ControlPart::Entire:
case ControlPart::ButtonDown:
{
rc.origin.y += (rc.size.height - LISTBOX_HEIGHT + 1) / 2;
rc.size.height = LISTBOX_HEIGHT;
NSPopUpButtonCell* pBtn = pInst->mpPopUpButtonCell;
[pBtn setTitle: @""];
[pBtn setEnabled: getEnabled(nState, mpFrame)];
[pBtn setFocusRingType: NSFocusRingTypeExterior];
[pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
if (nState & ControlState::DEFAULT)
[pBtn setKeyEquivalent: @"\r"];
else
[pBtn setKeyEquivalent: @""];
{
rc.size.width += 1;
}
const bool bFocused(nState & ControlState::FOCUSED);
paintCell(pBtn, rc, bFocused, context, nullptr);
bOK = true;
break;
}
case ControlPart::ListboxWindow:
{
NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
NSScrollView* pBox = [[NSScrollView alloc] initWithFrame: rect];
[pBox setBorderType: NSLineBorder];
CGContextSaveGState(context);
CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
[NSGraphicsContext setCurrentContext: graphicsContext];
[pBox drawRect: rect];
[NSGraphicsContext setCurrentContext: savedContext];
CGContextRestoreGState(context);
[pBox release];
bOK = true;
break;
}
default:
break;
}
break;
case ControlType::Spinbox:
if (nPart == ControlPart::Entire)
{
// text field
rc.size.width -= SPIN_BUTTON_WIDTH + 4 * FOCUS_RING_WIDTH;
rc.size.height = EDITBOX_HEIGHT;
rc.origin.x += FOCUS_RING_WIDTH;
rc.origin.y += FOCUS_RING_WIDTH;
NSTextFieldCell* pEdit = pInst->mpTextFieldCell;
[pEdit setEnabled: YES];
[pEdit setBezeled: YES];
[pEdit setEditable: YES];
[pEdit setFocusRingType: NSFocusRingTypeExterior];
drawEditableBackground(context, rc);
const bool bFocused(nState & ControlState::FOCUSED);
paintCell(pEdit, rc, bFocused, context, mpFrame->getNSView());
// buttons
const SpinbuttonValue *pSpinButtonVal = (aValue.getType() == ControlType::SpinButtons)
? static_cast <const SpinbuttonValue *>(&aValue) : nullptr;
if (pSpinButtonVal)
{
ControlState nUpperState = pSpinButtonVal->mnUpperState;
ControlState nLowerState = pSpinButtonVal->mnLowerState;
rc.origin.x += rc.size.width + FOCUS_RING_WIDTH + 1;
rc.origin.y -= 1;
rc.size.width = SPIN_BUTTON_WIDTH;
rc.size.height = SPIN_LOWER_BUTTON_HEIGHT + SPIN_LOWER_BUTTON_HEIGHT;
NSStepperCell* pBtn = pInst->mpStepperCell;
[pBtn setTitle: @""];
[pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
[pBtn setEnabled: (nUpperState & ControlState::ENABLED || nLowerState & ControlState::ENABLED) ?
YES : NO];
[pBtn setFocusRingType: NSFocusRingTypeExterior];
[pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
const bool bSpinFocused(nUpperState & ControlState::FOCUSED || nLowerState & ControlState::FOCUSED);
paintCell(pBtn, rc, bSpinFocused, context, nullptr);
}
bOK = true;
}
break;
case ControlType::Frame:
{
DrawFrameFlags nStyle = static_cast<DrawFrameFlags>(aValue.getNumericVal());
if (nPart == ControlPart::Border)
{
if (!(nStyle & DrawFrameFlags::Menu) && !(nStyle & DrawFrameFlags::WindowBorder))
{
// strange effects start to happen when HIThemeDrawFrame meets the border of the window.
// These can be avoided by clipping to the boundary of the frame (see issue 84756)
if (rc.origin.y + rc.size.height >= mpFrame->GetHeight() - 3)
{
CGMutablePathRef rPath = CGPathCreateMutable();
CGPathAddRect(rPath, nullptr,
CGRectMake(0, 0, mpFrame->GetWidth() - 1, mpFrame->GetHeight() - 1));
CGContextBeginPath(context);
CGContextAddPath(context, rPath);
CGContextClip(context);
CGPathRelease(rPath);
}
HIThemeFrameDrawInfo aTextDrawInfo;
aTextDrawInfo.version = 0;
aTextDrawInfo.kind = kHIThemeFrameListBox;
aTextDrawInfo.state = kThemeStateActive;
aTextDrawInfo.isFocused = false;
HIThemeDrawFrame(&rc, &aTextDrawInfo, context, kHIThemeOrientationNormal);
bOK = true;
}
}
}
break;
case ControlType::ListNet:
// do nothing as there isn't net for listviews on macOS
bOK = true;
break;
default:
break;
}
return bOK;
}
bool AquaSalGraphics::getNativeControlRegion(ControlType nType,
ControlPart nPart,
const tools::Rectangle &rControlRegion,
ControlState,
const ImplControlValue &aValue,
const OUString &,
tools::Rectangle &rNativeBoundingRegion,
tools::Rectangle &rNativeContentRegion)
{
bool toReturn = false;
tools::Rectangle aCtrlBoundRect(rControlRegion);
short x = aCtrlBoundRect.Left();
short y = aCtrlBoundRect.Top();
short w, h;
switch (nType)
{
case ControlType::Pushbutton:
case ControlType::Radiobutton:
case ControlType::Checkbox:
{
if (nType == ControlType::Pushbutton)
{
w = aCtrlBoundRect.GetWidth();
h = aCtrlBoundRect.GetHeight();
}
else
{
w = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH + RADIO_BUTTON_TEXT_SEPARATOR;
h = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH;
}
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
break;
case ControlType::LevelBar:
case ControlType::Progress:
{
tools::Rectangle aRect(aCtrlBoundRect);
if (aRect.GetHeight() < LARGE_PROGRESS_INDICATOR_HEIGHT)
aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
else
aRect.SetBottom(aRect.Top() + LARGE_PROGRESS_INDICATOR_HEIGHT - 1);
rNativeBoundingRegion = aRect;
rNativeContentRegion = aRect;
toReturn = true;
}
break;
case ControlType::IntroProgress:
{
tools::Rectangle aRect(aCtrlBoundRect);
aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
rNativeBoundingRegion = aRect;
rNativeContentRegion = aRect;
toReturn = true;
}
break;
case ControlType::Slider:
if (nPart == ControlPart::ThumbHorz)
{
w = SLIDER_WIDTH;
h = aCtrlBoundRect.GetHeight();
rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
else if (nPart == ControlPart::ThumbVert)
{
w = aCtrlBoundRect.GetWidth();
h = SLIDER_HEIGHT;
rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
break;
case ControlType::Scrollbar:
{
tools::Rectangle aRect;
if (AquaGetScrollRect(nPart, aCtrlBoundRect, aRect))
{
toReturn = true;
rNativeBoundingRegion = aRect;
rNativeContentRegion = aRect;
}
}
break;
case ControlType::TabItem:
{
w = aCtrlBoundRect.GetWidth() + 2 * TAB_TEXT_MARGIN;
h = TAB_HEIGHT + 2;
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
break;
case ControlType::Editbox:
{
const tools::Long nBorderThickness = FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
// tdf#144241 don't return a negative width, expand the region to the min osx width
w = std::max(nBorderThickness * 2, aCtrlBoundRect.GetWidth());
h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
w -= 2 * nBorderThickness;
h -= 2 * nBorderThickness;
x += nBorderThickness;
y += nBorderThickness;
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
break;
case ControlType::Combobox:
if (nPart == ControlPart::Entire)
{
w = aCtrlBoundRect.GetWidth();
h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
else if (nPart == ControlPart::ButtonDown)
{
w = COMBOBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
x += aCtrlBoundRect.GetWidth() - w;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
else if (nPart == ControlPart::SubEdit)
{
w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - COMBOBOX_BUTTON_WIDTH - COMBOBOX_BORDER_WIDTH
- 2 * COMBOBOX_TEXT_MARGIN;
h = COMBOBOX_HEIGHT - 2 * COMBOBOX_BORDER_WIDTH;
x += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH + COMBOBOX_TEXT_MARGIN;
y += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
break;
case ControlType::Listbox:
if (nPart == ControlPart::Entire)
{
w = aCtrlBoundRect.GetWidth();
h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
else if (nPart == ControlPart::ButtonDown)
{
w = LISTBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
x += aCtrlBoundRect.GetWidth() - w;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
else if (nPart == ControlPart::SubEdit)
{
w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - LISTBOX_BUTTON_WIDTH - LISTBOX_BORDER_WIDTH
- 2 * LISTBOX_TEXT_MARGIN;
h = LISTBOX_HEIGHT - 2 * LISTBOX_BORDER_WIDTH;
x += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH + LISTBOX_TEXT_MARGIN;
y += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
else if (nPart == ControlPart::ListboxWindow)
{
w = aCtrlBoundRect.GetWidth() - 2;
h = aCtrlBoundRect.GetHeight() - 2;
x += 1;
y += 1;
rNativeBoundingRegion = aCtrlBoundRect;
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
break;
case ControlType::Spinbox:
if (nPart == ControlPart::Entire)
{
w = aCtrlBoundRect.GetWidth();
h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
x += SPINBOX_OFFSET;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
else if (nPart == ControlPart::SubEdit)
{
w = aCtrlBoundRect.GetWidth() - 4 * FOCUS_RING_WIDTH - SPIN_BUTTON_WIDTH - 2 * EDITBOX_BORDER_WIDTH
- 2 * EDITBOX_INSET_MARGIN;
h = EDITBOX_HEIGHT - 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
x += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN + SPINBOX_OFFSET;
y += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
else if (nPart == ControlPart::ButtonUp)
{
w = SPIN_BUTTON_WIDTH + 2 * FOCUS_RING_WIDTH;
h = SPIN_UPPER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
else if (nPart == ControlPart::ButtonDown)
{
w = SPIN_BUTTON_WIDTH + 2 * FOCUS_RING_WIDTH;
h = SPIN_LOWER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
y += FOCUS_RING_WIDTH + SPIN_UPPER_BUTTON_HEIGHT;
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
break;
case ControlType::Frame:
{
DrawFrameStyle nStyle = static_cast<DrawFrameStyle>(aValue.getNumericVal() & 0x000f);
DrawFrameFlags nFlags = static_cast<DrawFrameFlags>(aValue.getNumericVal() & 0xfff0);
if (nPart == ControlPart::Border
&& !(nFlags & (DrawFrameFlags::Menu | DrawFrameFlags::WindowBorder | DrawFrameFlags::BorderWindowBorder)))
{
tools::Rectangle aRect(aCtrlBoundRect);
if (nStyle == DrawFrameStyle::DoubleIn)
{
aRect.AdjustLeft(1);
aRect.AdjustTop(1);
// rRect.Right() -= 1;
// rRect.Bottom() -= 1;
}
else
{
aRect.AdjustLeft(1);
aRect.AdjustTop(1);
aRect.AdjustRight(-1);
aRect.AdjustBottom(-1);
}
rNativeContentRegion = aRect;
rNativeBoundingRegion = aRect;
toReturn = true;
}
}
break;
case ControlType::Menubar:
case ControlType::MenuPopup:
if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
{
w=10;
h=10;
rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
toReturn = true;
}
break;
default:
break;
}
return toReturn;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */