12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816 |
- // Copyright 2020 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ash/capture_mode/capture_mode_session.h"
- #include <tuple>
- #include "ash/accessibility/accessibility_controller_impl.h"
- #include "ash/accessibility/magnifier/magnifier_glass.h"
- #include "ash/capture_mode/capture_label_view.h"
- #include "ash/capture_mode/capture_mode_bar_view.h"
- #include "ash/capture_mode/capture_mode_camera_controller.h"
- #include "ash/capture_mode/capture_mode_camera_preview_view.h"
- #include "ash/capture_mode/capture_mode_constants.h"
- #include "ash/capture_mode/capture_mode_controller.h"
- #include "ash/capture_mode/capture_mode_menu_group.h"
- #include "ash/capture_mode/capture_mode_session_focus_cycler.h"
- #include "ash/capture_mode/capture_mode_settings_view.h"
- #include "ash/capture_mode/capture_mode_toggle_button.h"
- #include "ash/capture_mode/capture_mode_type_view.h"
- #include "ash/capture_mode/capture_mode_util.h"
- #include "ash/capture_mode/capture_window_observer.h"
- #include "ash/capture_mode/folder_selection_dialog_controller.h"
- #include "ash/capture_mode/user_nudge_controller.h"
- #include "ash/constants/ash_features.h"
- #include "ash/display/mouse_cursor_event_filter.h"
- #include "ash/display/screen_orientation_controller.h"
- #include "ash/display/window_tree_host_manager.h"
- #include "ash/keyboard/ui/keyboard_ui_controller.h"
- #include "ash/projector/projector_controller_impl.h"
- #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
- #include "ash/public/cpp/shell_window_ids.h"
- #include "ash/resources/vector_icons/vector_icons.h"
- #include "ash/root_window_controller.h"
- #include "ash/shell.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/style/ash_color_provider.h"
- #include "ash/wm/mru_window_tracker.h"
- #include "ash/wm/tablet_mode/tablet_mode_controller.h"
- #include "ash/wm/window_dimmer.h"
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/check.h"
- #include "base/memory/ptr_util.h"
- #include "cc/paint/paint_flags.h"
- #include "ui/aura/client/aura_constants.h"
- #include "ui/aura/client/capture_client.h"
- #include "ui/aura/env.h"
- #include "ui/aura/window.h"
- #include "ui/aura/window_delegate.h"
- #include "ui/aura/window_observer.h"
- #include "ui/aura/window_tracker.h"
- #include "ui/base/cursor/cursor_factory.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/compositor/layer.h"
- #include "ui/compositor/layer_animation_element.h"
- #include "ui/compositor/layer_type.h"
- #include "ui/compositor/paint_recorder.h"
- #include "ui/compositor/scoped_layer_animation_settings.h"
- #include "ui/display/screen.h"
- #include "ui/display/types/display_constants.h"
- #include "ui/events/types/event_type.h"
- #include "ui/gfx/canvas.h"
- #include "ui/gfx/geometry/insets.h"
- #include "ui/gfx/geometry/point.h"
- #include "ui/gfx/geometry/rect.h"
- #include "ui/gfx/geometry/transform_util.h"
- #include "ui/gfx/paint_vector_icon.h"
- #include "ui/gfx/scoped_canvas.h"
- #include "ui/gfx/shadow_value.h"
- #include "ui/gfx/skia_paint_util.h"
- #include "ui/views/animation/animation_builder.h"
- #include "ui/views/background.h"
- #include "ui/views/controls/button/label_button.h"
- #include "ui/views/controls/label.h"
- #include "ui/views/widget/widget.h"
- #include "ui/wm/core/coordinate_conversion.h"
- #include "ui/wm/core/cursor_util.h"
- namespace ash {
- namespace {
- // The visual radius of the drag affordance circles which are shown while
- // resizing a drag region.
- constexpr int kAffordanceCircleRadiusDp = 4;
- // The hit radius of the drag affordance circles touch events.
- constexpr int kAffordanceCircleTouchHitRadiusDp = 16;
- // Capture region magnifier parameters.
- constexpr MagnifierGlass::Params kMagnifierParams{
- /*scale=*/2.f,
- /*radius=*/60,
- /*border_size=*/2,
- /*border_outline_thickness=*/0,
- /*border_color=*/SK_ColorWHITE,
- /*border_outline_color=*/SK_ColorTRANSPARENT,
- /*bottom_shadow=*/
- gfx::ShadowValue(gfx::Vector2d(0, 1),
- 2,
- SkColorSetARGB(0x4C, 0x00, 0x00, 0x00)),
- /*top_shadow=*/
- gfx::ShadowValue(gfx::Vector2d(0, 1),
- 3,
- SkColorSetARGB(0x26, 0x00, 0x00, 0x00))};
- constexpr int kSizeLabelBorderRadius = 4;
- constexpr int kSizeLabelHorizontalPadding = 8;
- // Values for the shadows of the capture region components.
- constexpr int kRegionAffordanceCircleShadow2Blur = 6;
- constexpr gfx::ShadowValue kRegionOutlineShadow(gfx::Vector2d(0, 0),
- 2,
- SkColorSetARGB(41, 0, 0, 0));
- constexpr gfx::ShadowValue kRegionAffordanceCircleShadow1(
- gfx::Vector2d(0, 1),
- 2,
- SkColorSetARGB(76, 0, 0, 0));
- constexpr gfx::ShadowValue kRegionAffordanceCircleShadow2(
- gfx::Vector2d(0, 2),
- kRegionAffordanceCircleShadow2Blur,
- SkColorSetARGB(38, 0, 0, 0));
- // Values of the focus ring draw around the region or affordance circles.
- constexpr int kFocusRingStrokeWidthDp = 2;
- constexpr int kFocusRingSpacingDp = 2;
- // When updating the capture region, request a repaint on the region and inset
- // such that the border, affordance circles and affordance circle shadows are
- // all repainted as well.
- constexpr int kDamageInsetDp = capture_mode::kCaptureRegionBorderStrokePx +
- kAffordanceCircleRadiusDp +
- kRegionAffordanceCircleShadow2Blur;
- // The minimum padding on each side of the capture region. If the capture button
- // cannot be placed in the center of the capture region and maintain this
- // padding, it will be placed below or above the capture region.
- constexpr int kCaptureRegionMinimumPaddingDp = 16;
- // Animation parameters needed when countdown starts.
- // The animation duration that the label fades out and scales down before count
- // down starts.
- constexpr base::TimeDelta kCaptureLabelCountdownStartDuration =
- base::Milliseconds(267);
- // The animation duration that the capture widgets (capture bar, capture
- // settings) fade out before count down starts.
- constexpr base::TimeDelta kCaptureWidgetFadeOutDuration =
- base::Milliseconds(167);
- // The animation duration that the fullscreen shield fades out before count down
- // starts.
- constexpr base::TimeDelta kCaptureShieldFadeOutDuration =
- base::Milliseconds(333);
- // If there is no text message was showing when count down starts, the label
- // widget will shrink down from 120% -> 100% and fade in.
- constexpr float kLabelScaleUpOnCountdown = 1.2;
- // The animation duration that the label fades out and scales up when going from
- // the selection phase to the fine tune phase.
- constexpr base::TimeDelta kCaptureLabelRegionPhaseChangeDuration =
- base::Milliseconds(167);
- // The delay before the label fades out and scales up.
- constexpr base::TimeDelta kCaptureLabelRegionPhaseChangeDelay =
- base::Milliseconds(67);
- // When going from the select region phase to the fine tune phase, the label
- // widget will scale up from 80% -> 100%.
- constexpr float kLabelScaleDownOnPhaseChange = 0.8;
- // Animation parameters for capture UI (capture bar, capture label) overlapping
- // the user capture region or camera preview. The default animation duration for
- // opacity changes to the capture UI.
- constexpr base::TimeDelta kCaptureUIOpacityChangeDuration =
- base::Milliseconds(100);
- // If the user is using keyboard only and they are on the selecting region
- // phase, they can create default region which is centered and sized to this
- // value times the root window's width and height.
- constexpr float kRegionDefaultRatio = 0.24f;
- // Mouse cursor warping is disabled when the capture source is a custom region.
- // Sets the mouse warp status to |enable| and return the original value.
- bool SetMouseWarpEnabled(bool enable) {
- auto* mouse_cursor_filter = Shell::Get()->mouse_cursor_filter();
- const bool old_value = mouse_cursor_filter->mouse_warp_enabled();
- mouse_cursor_filter->set_mouse_warp_enabled(enable);
- return old_value;
- }
- // Gets the menu container inside |root|.
- aura::Window* GetParentContainer(aura::Window* root) {
- DCHECK(root);
- DCHECK(root->IsRootWindow());
- return root->GetChildById(kShellWindowId_MenuContainer);
- }
- // Returns the smallest rect that contains all of |points|.
- gfx::Rect GetRectEnclosingPoints(const std::vector<gfx::Point>& points) {
- DCHECK_GE(points.size(), 2u);
- int x = INT_MAX;
- int y = INT_MAX;
- int right = INT_MIN;
- int bottom = INT_MIN;
- for (const gfx::Point& point : points) {
- x = std::min(point.x(), x);
- y = std::min(point.y(), y);
- right = std::max(point.x(), right);
- bottom = std::max(point.y(), bottom);
- }
- return gfx::Rect(x, y, right - x, bottom - y);
- }
- // Returns the widget init params needed to create a widget associated with a
- // capture session.
- views::Widget::InitParams CreateWidgetParams(aura::Window* parent,
- const gfx::Rect& bounds,
- const std::string& name) {
- // Use a popup widget to get transient properties, such as not needing to
- // click on the widget first to get capture before receiving events.
- views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
- params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
- params.parent = parent;
- params.bounds = bounds;
- params.name = name;
- return params;
- }
- // Gets the root window associated |location_in_screen| if given, otherwise gets
- // the root window associated with the CursorManager.
- aura::Window* GetPreferredRootWindow(
- absl::optional<gfx::Point> location_in_screen = absl::nullopt) {
- int64_t display_id =
- (location_in_screen
- ? display::Screen::GetScreen()->GetDisplayNearestPoint(
- *location_in_screen)
- : Shell::Get()->cursor_manager()->GetDisplay())
- .id();
- // The Display object returned by CursorManager::GetDisplay may be stale, but
- // will have the correct id.
- DCHECK_NE(display::kInvalidDisplayId, display_id);
- return Shell::GetRootWindowForDisplayId(display_id);
- }
- // In fullscreen or window capture mode, the mouse will change to a camera
- // image icon if we're capturing image, or a video record image icon if we're
- // capturing video.
- ui::Cursor GetCursorForFullscreenOrWindowCapture(bool capture_image) {
- ui::Cursor cursor(ui::mojom::CursorType::kCustom);
- const display::Display display =
- display::Screen::GetScreen()->GetDisplayNearestWindow(
- GetPreferredRootWindow());
- const float device_scale_factor = display.device_scale_factor();
- const gfx::ImageSkia* icon =
- ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
- capture_image ? IDR_CAPTURE_IMAGE_CURSOR : IDR_CAPTURE_VIDEO_CURSOR);
- SkBitmap bitmap = *icon->bitmap();
- gfx::Point hotspot(bitmap.width() / 2, bitmap.height() / 2);
- wm::ScaleAndRotateCursorBitmapAndHotpoint(
- device_scale_factor, display.panel_rotation(), &bitmap, &hotspot);
- auto* cursor_factory = ui::CursorFactory::GetInstance();
- cursor.SetPlatformCursor(
- cursor_factory->CreateImageCursor(cursor.type(), bitmap, hotspot));
- cursor.set_custom_bitmap(bitmap);
- cursor.set_custom_hotspot(hotspot);
- return cursor;
- }
- // Returns the expected cursor type for |position| in region capture.
- ui::mojom::CursorType GetCursorTypeForFineTunePosition(
- FineTunePosition position) {
- switch (position) {
- case FineTunePosition::kTopLeft:
- return ui::mojom::CursorType::kNorthWestResize;
- case FineTunePosition::kBottomRight:
- return ui::mojom::CursorType::kSouthEastResize;
- case FineTunePosition::kTopCenter:
- case FineTunePosition::kBottomCenter:
- return ui::mojom::CursorType::kNorthSouthResize;
- case FineTunePosition::kTopRight:
- return ui::mojom::CursorType::kNorthEastResize;
- case FineTunePosition::kBottomLeft:
- return ui::mojom::CursorType::kSouthWestResize;
- case FineTunePosition::kLeftCenter:
- case FineTunePosition::kRightCenter:
- return ui::mojom::CursorType::kEastWestResize;
- case FineTunePosition::kCenter:
- return ui::mojom::CursorType::kMove;
- default:
- return ui::mojom::CursorType::kCell;
- }
- }
- // Gets the amount of change that should happen to a region given |event_flags|.
- int GetArrowKeyPressChange(int event_flags) {
- if ((event_flags & ui::EF_SHIFT_DOWN) != 0)
- return capture_mode::kShiftArrowKeyboardRegionChangeDp;
- if ((event_flags & ui::EF_CONTROL_DOWN) != 0)
- return capture_mode::kCtrlArrowKeyboardRegionChangeDp;
- return capture_mode::kArrowKeyboardRegionChangeDp;
- }
- // Clips |out_bounds| to fit |rect|. Similar to
- // gfx::Rect::AdjustToFit() but does not shift the output rect to maintain the
- // rect size.
- void ClipRectToFit(gfx::Rect* out_bounds, const gfx::Rect& rect) {
- out_bounds->SetByBounds(std::max(rect.x(), out_bounds->x()),
- std::max(rect.y(), out_bounds->y()),
- std::min(rect.right(), out_bounds->right()),
- std::min(rect.bottom(), out_bounds->bottom()));
- }
- // Returns the `message_id` for the chromevox alert when capture session starts.
- int GetMessageIdForInitialCaptureSource(CaptureModeSource source) {
- switch (source) {
- case CaptureModeSource::kFullscreen:
- return IDS_ASH_SCREEN_CAPTURE_SOURCE_FULLSCREEN;
- case CaptureModeSource::kRegion:
- return IDS_ASH_SCREEN_CAPTURE_SOURCE_PARTIAL;
- default:
- return IDS_ASH_SCREEN_CAPTURE_SOURCE_WINDOW;
- }
- }
- void UpdateFloatingPanelBoundsIfNeeded() {
- Shell::Get()->accessibility_controller()->UpdateFloatingPanelBoundsIfNeeded();
- }
- views::Widget* GetCameraPreviewWidget() {
- return CaptureModeController::Get()
- ->camera_controller()
- ->camera_preview_widget();
- }
- bool ShouldPassEventToCameraPreview(ui::LocatedEvent* event) {
- auto* controller = CaptureModeController::Get();
- // If there's a video recording in progress, return false immediately, since
- // even camera preview exists, it doesn't belong to the current capture
- // session.
- if (controller->is_recording_in_progress())
- return false;
- auto* camera_preview_widget = GetCameraPreviewWidget();
- if (!camera_preview_widget || !camera_preview_widget->IsVisible())
- return false;
- if (controller->camera_controller()->is_drag_in_progress())
- return true;
- // If the event is targeted on the camera preview, even it's not located
- // on the camera preview, we should still pass the event to camera preview
- // to handle it. For example, when pressing on the resize button inside camera
- // preview, but release the press outside of camera preview, even the release
- // event is not on the camera preview, we should still pass the event to it,
- // otherwise camera preview will wait for the release event forever which will
- // make the regular drag for camera preview not work.
- auto* target = static_cast<aura::Window*>(event->target());
- if (camera_preview_widget->GetNativeWindow()->Contains(target))
- return true;
- return false;
- }
- // Returns true if the given `widget` intersects with the camera preview.
- // Otherwise, returns false;
- bool IsWidgetOverlappedWithCameraPreview(views::Widget* widget) {
- // Return false immediately if there's a video recording in propress since
- // the camera preview doesn't belong to the current capture session.
- if (CaptureModeController::Get()->is_recording_in_progress())
- return false;
- auto* camera_preview_widget = GetCameraPreviewWidget();
- if (!camera_preview_widget)
- return false;
- return camera_preview_widget->IsVisible() &&
- camera_preview_widget->GetLayer()->GetTargetOpacity() > 0.f &&
- camera_preview_widget->GetWindowBoundsInScreen().Intersects(
- widget->GetWindowBoundsInScreen());
- }
- } // namespace
- // -----------------------------------------------------------------------------
- // CaptureModeSession::CursorSetter:
- class CaptureModeSession::CursorSetter {
- public:
- CursorSetter()
- : cursor_manager_(Shell::Get()->cursor_manager()),
- original_cursor_(cursor_manager_->GetCursor()),
- original_cursor_visible_(cursor_manager_->IsCursorVisible()),
- original_cursor_locked_(cursor_manager_->IsCursorLocked()),
- current_orientation_(GetCurrentScreenOrientation()) {
- if (!cursor_manager_->IsMouseEventsEnabled())
- cursor_manager_->EnableMouseEvents();
- }
- CursorSetter(const CursorSetter&) = delete;
- CursorSetter& operator=(const CursorSetter&) = delete;
- ~CursorSetter() { ResetCursor(); }
- // Note that this will always make the cursor visible if it is not |kNone|.
- void UpdateCursor(const ui::Cursor& cursor) {
- if (original_cursor_locked_)
- return;
- if (in_cursor_update_)
- return;
- base::AutoReset<bool> auto_reset_in_cursor_update(&in_cursor_update_, true);
- const ui::mojom::CursorType current_cursor_type =
- cursor_manager_->GetCursor().type();
- const ui::mojom::CursorType new_cursor_type = cursor.type();
- const CaptureModeType capture_type = CaptureModeController::Get()->type();
- const float device_scale_factor =
- display::Screen::GetScreen()
- ->GetDisplayNearestWindow(GetPreferredRootWindow())
- .device_scale_factor();
- // For custom cursors, update the cursor if we need to change between image
- // capture and video capture, if the device scale factor changes, or if the
- // screen orientation changes.
- const chromeos::OrientationType orientation = GetCurrentScreenOrientation();
- const bool is_cursor_changed =
- current_cursor_type != new_cursor_type ||
- (current_cursor_type == ui::mojom::CursorType::kCustom &&
- (custom_cursor_capture_type_ != capture_type ||
- custom_cursor_device_scale_factor_ != device_scale_factor ||
- current_orientation_ != orientation));
- const bool is_cursor_visibility_changed =
- cursor_manager_->IsCursorVisible() !=
- (new_cursor_type != ui::mojom::CursorType::kNone);
- if (new_cursor_type == ui::mojom::CursorType::kCustom) {
- custom_cursor_capture_type_ = capture_type;
- custom_cursor_device_scale_factor_ = device_scale_factor;
- }
- current_orientation_ = orientation;
- if (!is_cursor_changed && !is_cursor_visibility_changed)
- return;
- if (cursor_manager_->IsCursorLocked())
- cursor_manager_->UnlockCursor();
- if (new_cursor_type == ui::mojom::CursorType::kNone) {
- cursor_manager_->HideCursor();
- } else {
- cursor_manager_->SetCursor(cursor);
- cursor_manager_->ShowCursor();
- }
- cursor_manager_->LockCursor();
- was_cursor_reset_to_original_ = false;
- }
- // Resets to its original cursor.
- void ResetCursor() {
- // Only unlock the cursor if it wasn't locked before.
- if (original_cursor_locked_)
- return;
- // Only reset cursor if it hasn't been reset before.
- if (was_cursor_reset_to_original_)
- return;
- if (cursor_manager_->IsCursorLocked())
- cursor_manager_->UnlockCursor();
- cursor_manager_->SetCursor(original_cursor_);
- if (original_cursor_visible_)
- cursor_manager_->ShowCursor();
- else
- cursor_manager_->HideCursor();
- was_cursor_reset_to_original_ = true;
- }
- bool IsCursorVisible() const { return cursor_manager_->IsCursorVisible(); }
- void HideCursor() {
- if (original_cursor_locked_ || !IsCursorVisible())
- return;
- if (cursor_manager_->IsCursorLocked())
- cursor_manager_->UnlockCursor();
- cursor_manager_->HideCursor();
- cursor_manager_->LockCursor();
- was_cursor_reset_to_original_ = false;
- }
- bool IsUsingCustomCursor(CaptureModeType type) const {
- return cursor_manager_->GetCursor().type() ==
- ui::mojom::CursorType::kCustom &&
- custom_cursor_capture_type_ == type;
- }
- private:
- wm::CursorManager* const cursor_manager_;
- const gfx::NativeCursor original_cursor_;
- const bool original_cursor_visible_;
- // If the original cursor is already locked, don't make any changes to it.
- const bool original_cursor_locked_;
- // The current custom cursor type. kImage if we're using image capture icon as
- // the mouse cursor, and kVideo if we're using video record icon as the mouse
- // cursor.
- CaptureModeType custom_cursor_capture_type_ = CaptureModeType::kImage;
- // Records the current device scale factor. If the DSF changes, we will need
- // to update the cursor if we're using a custom cursor.
- float custom_cursor_device_scale_factor_ = 1.f;
- // Records the current screen orientation. If screen orientation changes, we
- // will need to update the cursor if we're using custom cursor.
- chromeos::OrientationType current_orientation_;
- // True if the cursor has reset back to its original cursor. It's to prevent
- // Reset() from setting the cursor to |original_cursor_| more than once.
- bool was_cursor_reset_to_original_ = true;
- // True if the cursor is currently being updated. This is to prevent
- // UpdateCursor() is called nestly more than once and the mouse is locked
- // multiple times.
- bool in_cursor_update_ = false;
- };
- // -----------------------------------------------------------------------------
- // CaptureModeSession::ParentContainerObserver:
- // The observer class to observer window added to or removed from the parent
- // container `kShellWindowId_MenuContainer`. Capture UIs (capture bar, capture
- // label, capture settings, camera preview) are all parented to the parent
- // container, thus whenever there's a window added or removed, we need to call
- // `RefreshStackingOrder` to ensure the stacking order is correct for them.
- class CaptureModeSession::ParentContainerObserver
- : public aura::WindowObserver {
- public:
- ParentContainerObserver(aura::Window* parent_container,
- CaptureModeSession* capture_mode_session)
- : parent_container_(parent_container),
- capture_mode_session_(capture_mode_session) {
- parent_container_->AddObserver(this);
- }
- ParentContainerObserver(const ParentContainerObserver&) = delete;
- ParentContainerObserver& operator=(const ParentContainerObserver&) = delete;
- ~ParentContainerObserver() override {
- if (parent_container_)
- parent_container_->RemoveObserver(this);
- }
- // aura::WindowObserver:
- void OnWindowAdded(aura::Window* window) override {
- capture_mode_session_->RefreshStackingOrder();
- capture_mode_session_->MaybeUpdateCaptureUisOpacity();
- }
- void OnWindowRemoved(aura::Window* window) override {
- capture_mode_session_->RefreshStackingOrder();
- capture_mode_session_->MaybeUpdateCaptureUisOpacity();
- }
- void OnWindowDestroying(aura::Window* window) override {
- parent_container_->RemoveObserver(this);
- parent_container_ = nullptr;
- }
- private:
- aura::Window* parent_container_;
- // Pointer to current capture session. Not nullptr during this lifecycle.
- // Capture session owns `this`.
- CaptureModeSession* const capture_mode_session_;
- };
- // -----------------------------------------------------------------------------
- // CaptureModeSession:
- CaptureModeSession::CaptureModeSession(CaptureModeController* controller,
- bool projector_mode)
- : controller_(controller),
- current_root_(GetPreferredRootWindow()),
- magnifier_glass_(kMagnifierParams),
- is_in_projector_mode_(projector_mode),
- cursor_setter_(std::make_unique<CursorSetter>()),
- focus_cycler_(std::make_unique<CaptureModeSessionFocusCycler>(this)),
- capture_toast_controller_(this) {}
- CaptureModeSession::~CaptureModeSession() = default;
- void CaptureModeSession::Initialize() {
- // Trigger this before creating `capture_mode_bar_widget_` as we want to read
- // out this message before reading out the first view of
- // `capture_mode_bar_widget_`.
- capture_mode_util::TriggerAccessibilityAlert(l10n_util::GetStringFUTF8(
- IDS_ASH_SCREEN_CAPTURE_ALERT_OPEN,
- l10n_util::GetStringUTF16(
- GetMessageIdForInitialCaptureSource(controller_->source())),
- l10n_util::GetStringUTF16(
- controller_->type() == CaptureModeType::kImage
- ? IDS_ASH_SCREEN_CAPTURE_TYPE_SCREENSHOT
- : IDS_ASH_SCREEN_CAPTURE_TYPE_SCREEN_RECORDING)));
- // A context menu may have input capture when entering a session. Remove
- // capture from it, otherwise subsequent mouse events will cause it to close,
- // and then we won't be able to take a screenshot of the menu. Store it so we
- // can return capture to it when exiting the session.
- // Note that some windows gets destroyed when they lose the capture (e.g. a
- // window created for capturing events while drag-drop in progress), so we
- // need to account for that.
- auto* capture_client = aura::client::GetCaptureClient(current_root_);
- input_capture_window_ = capture_client->GetCaptureWindow();
- if (input_capture_window_) {
- aura::WindowTracker tracker({input_capture_window_});
- capture_client->ReleaseCapture(input_capture_window_);
- if (tracker.windows().empty())
- input_capture_window_ = nullptr;
- else
- input_capture_window_->AddObserver(this);
- }
- SetLayer(std::make_unique<ui::Layer>(ui::LAYER_TEXTURED));
- layer()->SetFillsBoundsOpaquely(false);
- layer()->set_delegate(this);
- auto* parent = GetParentContainer(current_root_);
- parent_container_observer_ =
- std::make_unique<ParentContainerObserver>(parent, this);
- parent->layer()->Add(layer());
- layer()->SetBounds(parent->bounds());
- layer()->SetName("CaptureModeSession");
- // The last region selected could have been on a larger display. Ensure that
- // the region is not larger than the current display.
- ClampCaptureRegionToRootWindowSize();
- capture_mode_bar_widget_->Init(CreateWidgetParams(
- parent,
- CaptureModeBarView::GetBounds(current_root_, is_in_projector_mode_),
- "CaptureModeBarWidget"));
- capture_mode_bar_view_ = capture_mode_bar_widget_->SetContentsView(
- std::make_unique<CaptureModeBarView>(is_in_projector_mode_));
- capture_mode_bar_widget_->GetNativeWindow()->SetTitle(
- l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_A11Y_TITLE));
- capture_mode_bar_widget_->Show();
- // Advance focus once if spoken feedback is on so that the capture bar takes
- // spoken feedback focus.
- if (Shell::Get()->accessibility_controller()->spoken_feedback().enabled())
- focus_cycler_->AdvanceFocus(/*reverse=*/false);
- UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
- UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
- /*is_touch=*/false);
- if (controller_->source() == CaptureModeSource::kWindow)
- capture_window_observer_ = std::make_unique<CaptureWindowObserver>(this);
- UpdateRootWindowDimmers();
- TabletModeController::Get()->AddObserver(this);
- display_observer_.emplace(this);
- // Our event handling code assumes the capture bar widget has been initialized
- // already. So we start handling events after everything has been setup.
- aura::Env::GetInstance()->AddPreTargetHandler(
- this, ui::EventTarget::Priority::kSystem);
- UpdateFloatingPanelBoundsIfNeeded();
- // call `OnCaptureTypeChanged` after capture bar's initialization is done
- // instead of in the initialization of the capture mode type view, since
- // `OnCaptureTypeChanged` may trigger `ShowCaptureToast` which depends on the
- // capture bar.
- // Also please note we should call `OnCaptureTypeChanged` in
- // `CaptureModeTypeView` instead of `CaptureModeSession`, since this is during
- // the initialization of the capture session, the type change is not triggered
- // by the user.
- capture_mode_bar_view_->capture_type_view()->OnCaptureTypeChanged(
- controller_->type());
- MaybeCreateUserNudge();
- if (is_in_projector_mode_)
- controller_->camera_controller()->MaybeSelectFirstCamera();
- Shell::Get()->AddShellObserver(this);
- }
- void CaptureModeSession::Shutdown() {
- is_shutting_down_ = true;
- aura::Env::GetInstance()->RemovePreTargetHandler(this);
- display_observer_.reset();
- user_nudge_controller_.reset();
- TabletModeController::Get()->RemoveObserver(this);
- if (input_capture_window_) {
- input_capture_window_->RemoveObserver(this);
- aura::client::GetCaptureClient(current_root_)
- ->SetCapture(input_capture_window_);
- }
- // This may happen if we hit esc while dragging.
- if (old_mouse_warp_status_)
- SetMouseWarpEnabled(*old_mouse_warp_status_);
- // Close all widgets immediately to avoid having them show up in the captured
- // screenshots or video.
- for (auto* widget : GetAvailableWidgets())
- widget->CloseNow();
- if (a11y_alert_on_session_exit_) {
- capture_mode_util::TriggerAccessibilityAlert(
- IDS_ASH_SCREEN_CAPTURE_ALERT_CLOSE);
- }
- UpdateFloatingPanelBoundsIfNeeded();
- if (!is_stopping_to_start_video_recording_) {
- // Stopping the session for any reason other than starting video recording
- // means a cancellation to an ongoing projector session (if any).
- if (is_in_projector_mode_)
- ProjectorControllerImpl::Get()->OnRecordingStartAborted();
- // Kill the camera preview when the capture mode session ends without
- // starting any recording.
- if (!controller_->is_recording_in_progress())
- controller_->camera_controller()->SetShouldShowPreview(false);
- }
- Shell::Get()->RemoveShellObserver(this);
- }
- aura::Window* CaptureModeSession::GetSelectedWindow() const {
- return capture_window_observer_ ? capture_window_observer_->window()
- : nullptr;
- }
- void CaptureModeSession::A11yAlertCaptureSource(bool trigger_now) {
- auto* controller = CaptureModeController::Get();
- const bool is_capturing_image = controller->type() == CaptureModeType::kImage;
- std::string message;
- switch (controller->source()) {
- case CaptureModeSource::kFullscreen:
- message = l10n_util::GetStringUTF8(
- is_capturing_image
- ? IDS_ASH_SCREEN_CAPTURE_ALERT_FULLSCREEN_SCREENSHOT
- : IDS_ASH_SCREEN_CAPTURE_ALERT_FULLSCREEN_RECORD);
- break;
- case CaptureModeSource::kRegion:
- if (!controller->user_capture_region().IsEmpty()) {
- message = l10n_util::GetStringUTF8(
- is_capturing_image ? IDS_ASH_SCREEN_CAPTURE_ALERT_REGION_SCREENSHOT
- : IDS_ASH_SCREEN_CAPTURE_ALERT_REGION_RECORD);
- }
- break;
- case CaptureModeSource::kWindow:
- // Selected window could be non-empty when switching to capture type.
- if (GetSelectedWindow()) {
- message = l10n_util::GetStringUTF8(
- is_capturing_image ? IDS_ASH_SCREEN_CAPTURE_ALERT_WINDOW_SCREENSHOT
- : IDS_ASH_SCREEN_CAPTURE_ALERT_WINDOW_RECORD);
- }
- break;
- }
- if (!message.empty()) {
- if (trigger_now)
- capture_mode_util::TriggerAccessibilityAlert(message);
- else
- capture_mode_util::TriggerAccessibilityAlertSoon(message);
- }
- }
- void CaptureModeSession::A11yAlertCaptureType() {
- capture_mode_util::TriggerAccessibilityAlert(
- CaptureModeController::Get()->type() == CaptureModeType::kImage
- ? IDS_ASH_SCREEN_CAPTURE_ALERT_SELECT_TYPE_IMAGE
- : IDS_ASH_SCREEN_CAPTURE_ALERT_SELECT_TYPE_VIDEO);
- A11yAlertCaptureSource(/*trigger_now=*/false);
- }
- void CaptureModeSession::OnCaptureSourceChanged(CaptureModeSource new_source) {
- capture_source_changed_ = true;
- if (new_source == CaptureModeSource::kWindow)
- capture_window_observer_ = std::make_unique<CaptureWindowObserver>(this);
- else
- capture_window_observer_.reset();
- if (new_source == CaptureModeSource::kRegion)
- num_capture_region_adjusted_ = 0;
- capture_mode_bar_view_->OnCaptureSourceChanged(new_source);
- UpdateDimensionsLabelWidget(/*is_resizing=*/false);
- layer()->SchedulePaint(layer()->bounds());
- UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
- UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
- /*is_touch=*/false);
- if (focus_cycler_->RegionGroupFocused())
- focus_cycler_->ClearFocus();
- A11yAlertCaptureSource(/*trigger_now=*/true);
- MaybeReparentCameraPreviewWidget();
- }
- void CaptureModeSession::OnCaptureTypeChanged(CaptureModeType new_type) {
- capture_mode_bar_view_->OnCaptureTypeChanged(new_type);
- UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
- UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
- /*is_touch=*/false);
- A11yAlertCaptureType();
- }
- void CaptureModeSession::OnWaitingForDlpConfirmationStarted() {
- is_waiting_for_dlp_confirmation_ = true;
- HideAllUis();
- }
- void CaptureModeSession::OnWaitingForDlpConfirmationEnded(bool reshow_uis) {
- is_waiting_for_dlp_confirmation_ = false;
- if (reshow_uis)
- ShowAllUis();
- }
- void CaptureModeSession::SetSettingsMenuShown(bool shown) {
- capture_mode_bar_view_->SetSettingsMenuShown(shown);
- if (!shown) {
- capture_mode_settings_widget_.reset();
- capture_mode_settings_view_ = nullptr;
- // After closing CaptureMode settings view, show CaptureLabel view if it has
- // been hidden.
- if (capture_label_widget_ && !capture_label_widget_->IsVisible())
- capture_label_widget_->Show();
- return;
- }
- if (!capture_mode_settings_widget_) {
- auto* parent = GetParentContainer(current_root_);
- capture_mode_settings_widget_ = std::make_unique<views::Widget>();
- MaybeDismissUserNudgeForever();
- capture_toast_controller_.DismissCurrentToastIfAny();
- capture_mode_settings_widget_->Init(CreateWidgetParams(
- parent, CaptureModeSettingsView::GetBounds(capture_mode_bar_view_),
- "CaptureModeSettingsWidget"));
- capture_mode_settings_view_ =
- capture_mode_settings_widget_->SetContentsView(
- std::make_unique<CaptureModeSettingsView>(this,
- is_in_projector_mode_));
- OnCaptureFolderMayHaveChanged();
- parent->layer()->StackAtTop(capture_mode_settings_widget_->GetLayer());
- focus_cycler_->OnSettingsMenuWidgetCreated();
- if (capture_label_widget_ && capture_label_widget_->IsVisible()) {
- // Hide CaptureLabel view if it overlaps with CaptureMode settings view.
- if (capture_mode_settings_widget_->GetWindowBoundsInScreen().Intersects(
- capture_label_widget_->GetWindowBoundsInScreen())) {
- capture_label_widget_->Hide();
- }
- }
- capture_mode_settings_widget_->GetNativeWindow()->SetTitle(
- l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_SETTINGS_A11Y_TITLE));
- capture_mode_settings_widget_->Show();
- }
- }
- void CaptureModeSession::ReportSessionHistograms() {
- if (controller_->source() == CaptureModeSource::kRegion) {
- RecordNumberOfCaptureRegionAdjustments(num_capture_region_adjusted_,
- is_in_projector_mode_);
- }
- num_capture_region_adjusted_ = 0;
- RecordCaptureModeSwitchesFromInitialMode(capture_source_changed_);
- RecordCaptureModeConfiguration(controller_->type(), controller_->source(),
- controller_->enable_audio_recording(),
- is_in_projector_mode_);
- }
- void CaptureModeSession::StartCountDown(
- base::OnceClosure countdown_finished_callback) {
- DCHECK(capture_label_widget_);
- // Show CaptureLabel view if it has been hidden. Since `capture_label_widget_`
- // is the only widget which should be shown on 3-seconds count down starts,
- // there's no need to consider if it insects with
- // `capture_mode_settings_widget_` or not here.
- if (!capture_label_widget_->IsVisible())
- capture_label_widget_->Show();
- CaptureLabelView* label_view =
- static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
- label_view->StartCountDown(std::move(countdown_finished_callback));
- UpdateCaptureLabelWidgetBounds(CaptureLabelAnimation::kCountdownStart);
- UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
- /*is_touch=*/false);
- // Fade out the capture bar, capture settings and capture toast if they exist.
- std::vector<ui::Layer*> layers_to_fade_out{
- capture_mode_bar_widget_->GetLayer()};
- if (capture_mode_settings_widget_)
- layers_to_fade_out.push_back(capture_mode_settings_widget_->GetLayer());
- if (auto* toast_layer = capture_toast_controller_.MaybeGetToastLayer())
- layers_to_fade_out.push_back(toast_layer);
- for (auto* layer : layers_to_fade_out) {
- ui::ScopedLayerAnimationSettings layer_settings(layer->GetAnimator());
- layer_settings.SetTransitionDuration(kCaptureWidgetFadeOutDuration);
- layer_settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
- layer_settings.SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
- layer->SetOpacity(0.f);
- }
- // Do a repaint to hide the affordance circles.
- RepaintRegion();
- // Fade out the shield if it's recording fullscreen.
- if (controller_->source() == CaptureModeSource::kFullscreen) {
- ui::ScopedLayerAnimationSettings shield_settings(layer()->GetAnimator());
- shield_settings.SetTransitionDuration(kCaptureShieldFadeOutDuration);
- shield_settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
- shield_settings.SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
- layer()->SetOpacity(0.f);
- }
- }
- void CaptureModeSession::OpenFolderSelectionDialog() {
- DCHECK(!folder_selection_dialog_controller_);
- folder_selection_dialog_controller_ =
- std::make_unique<FolderSelectionDialogController>(/*delegate=*/this,
- current_root_);
- // We have to call `MaybeUpdateCameraPreviewBounds()` here after
- // `folder_selection_dialog_controller_` has been set, since
- // `CalculateCameraPreviewTargetVisibility()` checks its existence to
- // determine the target visibility of the camera preview. We cannot however
- // call it in `OnSelectionWindowAdded()` since this function can be called
- // inside the constructor of `FolderSelectionDialogController` before the
- // `folder_selection_dialog_controller_` member is set above.
- MaybeUpdateCameraPreviewBounds();
- }
- bool CaptureModeSession::IsInCountDownAnimation() const {
- if (is_shutting_down_)
- return false;
- CaptureLabelView* label_view =
- static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
- return label_view->IsInCountDownAnimation();
- }
- void CaptureModeSession::OnCaptureFolderMayHaveChanged() {
- if (!capture_mode_settings_widget_)
- return;
- DCHECK(capture_mode_settings_view_);
- capture_mode_settings_view_->OnCaptureFolderMayHaveChanged();
- MaybeUpdateSettingsBounds();
- }
- void CaptureModeSession::OnDefaultCaptureFolderSelectionChanged() {
- if (!capture_mode_settings_widget_)
- return;
- DCHECK(capture_mode_settings_view_);
- capture_mode_settings_view_->OnDefaultCaptureFolderSelectionChanged();
- }
- aura::Window* CaptureModeSession::GetCameraPreviewParentWindow() const {
- auto* controller = CaptureModeController::Get();
- DCHECK(!controller->is_recording_in_progress());
- auto* menu_container =
- current_root_->GetChildById(kShellWindowId_MenuContainer);
- auto* unparented_container =
- current_root_->GetChildById(kShellWindowId_UnparentedContainer);
- switch (controller->source()) {
- case CaptureModeSource::kFullscreen:
- return menu_container;
- case CaptureModeSource::kRegion:
- return controller_->user_capture_region().IsEmpty() ? unparented_container
- : menu_container;
- case CaptureModeSource::kWindow:
- aura::Window* selected_window = GetSelectedWindow();
- return selected_window ? selected_window : unparented_container;
- }
- }
- gfx::Rect CaptureModeSession::GetCameraPreviewConfineBounds() const {
- auto* controller = CaptureModeController::Get();
- DCHECK(!controller->is_recording_in_progress());
- switch (controller->source()) {
- case CaptureModeSource::kFullscreen: {
- auto* parent = GetCameraPreviewParentWindow();
- DCHECK(parent);
- return display::Screen::GetScreen()
- ->GetDisplayNearestWindow(parent)
- .work_area();
- }
- case CaptureModeSource::kWindow: {
- aura::Window* selected_window = GetSelectedWindow();
- return selected_window ? gfx::Rect(selected_window->bounds().size())
- : gfx::Rect();
- }
- case CaptureModeSource::kRegion: {
- gfx::Rect capture_region = controller->user_capture_region();
- wm::ConvertRectToScreen(current_root_, &capture_region);
- return capture_region;
- }
- }
- }
- bool CaptureModeSession::CalculateCameraPreviewTargetVisibility() const {
- // A screenshot-only-session opened during recording should not affect the
- // visibility of a camera preview created for that on-going recording.
- if (controller_->is_recording_in_progress())
- return true;
- // The camera preview should be hidden while the folder selection dialog is
- // shown in order to avoid it being on top of the dialog and blocking
- // interacting with it. This is consistent with what we do with the rest of
- // the capture mode UIs (see OnSelectionWindowAdded()).
- if (folder_selection_dialog_controller_)
- return false;
- // For fullscreen and window sources, the visibility of the camera preview is
- // determined by the preview's size specs, and whether there's a window source
- // selected. We only care about region sources here, since the visibility of
- // the preview in this case depends on what's happening to the region during
- // this session.
- if (controller_->source() != CaptureModeSource::kRegion)
- return true;
- if (controller_->user_capture_region().IsEmpty())
- return false;
- // Adjusting the region from any of its affordance points should result in
- // hiding the preview. However, dragging it around from its center should not
- // change the visibility.
- return !is_drag_in_progress_ ||
- fine_tune_position_ == FineTunePosition::kCenter;
- }
- void CaptureModeSession::OnPaintLayer(const ui::PaintContext& context) {
- // If the drag of camera preview is in progress, we will hide other capture
- // UIs (capture bar, capture label), but we should still paint the layer to
- // indicate the capture surface where user can drag camera preview on.
- if (!is_all_uis_visible_ &&
- !controller_->camera_controller()->is_drag_in_progress()) {
- return;
- }
- ui::PaintRecorder recorder(context, layer()->size());
- recorder.canvas()->DrawColor(capture_mode::kDimmingShieldColor);
- PaintCaptureRegion(recorder.canvas());
- }
- void CaptureModeSession::OnKeyEvent(ui::KeyEvent* event) {
- // We don't consume any events while a DLP system-modal dialog might be shown,
- // so that the user may interact with it.
- if (is_waiting_for_dlp_confirmation_)
- return;
- if (folder_selection_dialog_controller_) {
- if (folder_selection_dialog_controller_->ShouldConsumeEvent(event))
- event->StopPropagation();
- return;
- }
- if (event->type() != ui::ET_KEY_PRESSED)
- return;
- auto* camera_preview_view =
- controller_->camera_controller()->camera_preview_view();
- if (camera_preview_view && camera_preview_view->MaybeHandleKeyEvent(event)) {
- event->StopPropagation();
- return;
- }
- // We create an owned heap-allocated boolean to pass it to `deferred_runner`
- // and hold a pointer to the boolean to be able to change its value to control
- // whether the deferred runner should call `MaybeUpdateCaptureUisOpacity` or
- // not.
- auto should_update_opacity = std::make_unique<bool>(false);
- auto* should_update_opacity_ptr = should_update_opacity.get();
- // Run at the exit of this function to update opacity of capture UIs when
- // necessary.
- base::ScopedClosureRunner deferred_runner(base::BindOnce(
- [](base::WeakPtr<CaptureModeSession> session,
- std::unique_ptr<bool> should_update_opacity) {
- if (*should_update_opacity && session)
- session->MaybeUpdateCaptureUisOpacity();
- },
- weak_ptr_factory_.GetWeakPtr(), std::move(should_update_opacity)));
- ui::KeyboardCode key_code = event->key_code();
- switch (key_code) {
- case ui::VKEY_ESCAPE: {
- event->StopPropagation();
- *should_update_opacity_ptr = true;
- // We only dismiss the settings menu or clear the focus on ESC key if the
- // count down is not in progress.
- const bool is_in_count_down = IsInCountDownAnimation();
- if (capture_mode_settings_widget_ && !is_in_count_down)
- SetSettingsMenuShown(false);
- else if (focus_cycler_->HasFocus() && !is_in_count_down)
- focus_cycler_->ClearFocus();
- else if (can_exit_on_escape_)
- controller_->Stop(); // `this` is destroyed here.
- return;
- }
- case ui::VKEY_RETURN: {
- event->StopPropagation();
- if (!IsInCountDownAnimation())
- DoPerformCapture(); // `this` can be deleted after this.
- return;
- }
- case ui::VKEY_SPACE: {
- event->StopPropagation();
- event->SetHandled();
- if (focus_cycler_->OnSpacePressed()) {
- *should_update_opacity_ptr = true;
- return;
- }
- // Create a default region if we are in region mode and there is no
- // existing region.
- if (controller_->source() == CaptureModeSource::kRegion &&
- controller_->user_capture_region().IsEmpty()) {
- SelectDefaultRegion();
- }
- return;
- }
- case ui::VKEY_TAB: {
- event->StopPropagation();
- event->SetHandled();
- focus_cycler_->AdvanceFocus(/*reverse=*/event->IsShiftDown());
- *should_update_opacity_ptr = true;
- return;
- }
- case ui::VKEY_UP:
- case ui::VKEY_DOWN: {
- event->StopPropagation();
- event->SetHandled();
- UpdateRegionVertically(/*up=*/key_code == ui::VKEY_UP, event->flags());
- return;
- }
- case ui::VKEY_LEFT:
- case ui::VKEY_RIGHT: {
- event->StopPropagation();
- event->SetHandled();
- UpdateRegionHorizontally(/*left=*/key_code == ui::VKEY_LEFT,
- event->flags());
- return;
- }
- default:
- return;
- }
- }
- void CaptureModeSession::OnMouseEvent(ui::MouseEvent* event) {
- // We don't consume any events while a DLP system-modal dialog might be shown,
- // so that the user may interact with it.
- if (is_waiting_for_dlp_confirmation_)
- return;
- OnLocatedEvent(event, /*is_touch=*/false);
- }
- void CaptureModeSession::OnTouchEvent(ui::TouchEvent* event) {
- // We don't consume any events while a DLP system-modal dialog might be shown,
- // so that the user may interact with it.
- if (is_waiting_for_dlp_confirmation_)
- return;
- OnLocatedEvent(event, /*is_touch=*/true);
- }
- void CaptureModeSession::OnTabletModeStarted() {
- UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
- UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
- /*is_touch=*/false);
- }
- void CaptureModeSession::OnTabletModeEnded() {
- UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
- UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
- /*is_touch=*/false);
- }
- void CaptureModeSession::OnWindowDestroying(aura::Window* window) {
- DCHECK_EQ(window, input_capture_window_);
- input_capture_window_->RemoveObserver(this);
- input_capture_window_ = nullptr;
- }
- void CaptureModeSession::OnRootWindowWillShutdown(aura::Window* root_window) {
- if (root_window == current_root_) {
- // There should always be a primary root window.
- DCHECK_NE(Shell::GetPrimaryRootWindow(), current_root_);
- MaybeChangeRoot(Shell::GetPrimaryRootWindow());
- }
- }
- void CaptureModeSession::OnDisplayMetricsChanged(
- const display::Display& display,
- uint32_t metrics) {
- if (!(metrics &
- (DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_ROTATION |
- DISPLAY_METRIC_DEVICE_SCALE_FACTOR | DISPLAY_METRIC_WORK_AREA))) {
- return;
- }
- EndSelection();
- UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
- /*is_touch=*/false);
- // Ensure the region still fits the root window after display changes.
- ClampCaptureRegionToRootWindowSize();
- // Update the bounds of all created widgets and repaint the entire layer.
- auto* parent = GetParentContainer(current_root_);
- DCHECK_EQ(parent->layer(), layer()->parent());
- layer()->SetBounds(parent->bounds());
- // We need to update the capture bar bounds first and then settings bounds.
- // The sequence matters here since settings bounds depend on capture bar
- // bounds.
- RefreshBarWidgetBounds();
- MaybeUpdateSettingsBounds();
- // Only need to update the camera preview's bounds if the capture source is
- // `kFullscreen`, since `ClampCaptureRegionToRootWindowSize` will take care of
- // it if the source is `kRegion`.
- // `CaptureWindowObserver::OnWindowBoundsChanged` will take care of it if the
- // source is `kWindow`.
- if (controller_->source() == CaptureModeSource::kFullscreen &&
- !controller_->is_recording_in_progress()) {
- controller_->camera_controller()->MaybeUpdatePreviewWidget();
- }
- if (capture_label_widget_)
- UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
- layer()->SchedulePaint(layer()->bounds());
- MaybeUpdateCaptureUisOpacity();
- }
- void CaptureModeSession::OnFolderSelected(const base::FilePath& path) {
- CaptureModeController::Get()->SetCustomCaptureFolder(path);
- if (controller_->GetCurrentCaptureFolder().is_default_downloads_folder) {
- RecordSwitchToDefaultFolderReason(
- CaptureModeSwitchToDefaultReason::
- kUserSelectedFromFolderSelectionDialog);
- }
- }
- void CaptureModeSession::OnSelectionWindowAdded() {
- // Hide all the capture session UIs so that they don't show on top of the
- // selection dialog window and block it.
- HideAllUis();
- }
- void CaptureModeSession::OnSelectionWindowClosed() {
- DCHECK(folder_selection_dialog_controller_);
- ShowAllUis();
- const bool did_user_select_a_folder =
- folder_selection_dialog_controller_->did_user_select_a_folder();
- folder_selection_dialog_controller_.reset();
- // This has to be called here after the `folder_selection_dialog_controller_`
- // member was reset above, since `CalculateCameraPreviewTargetVisibility()`
- // relies on its existence or lack thereof to determine the target visibility
- // of the camera preview.
- MaybeUpdateCameraPreviewBounds();
- // If the selection window is closed by user selecting a folder, no need to
- // update the capture folder settings menu here, since it's covered by
- // `SetCustomCaptureFolder` via `OnFolderSelected`.
- if (!did_user_select_a_folder)
- OnCaptureFolderMayHaveChanged();
- // Explicitly hide any virtual keyboard that may have remained open from
- // interacting with the dialog selection window.
- keyboard::KeyboardUIController::Get()->HideKeyboardExplicitlyBySystem();
- }
- void CaptureModeSession::UpdateCursor(const gfx::Point& location_in_screen,
- bool is_touch) {
- // No need to update cursor if `cursor_setter_` has been reset because of
- // OpenFolderSelectionDialog.
- if (is_shutting_down_ || !cursor_setter_)
- return;
- // Hide mouse cursor in tablet mode.
- auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
- if (tablet_mode_controller->InTabletMode() &&
- !tablet_mode_controller->IsInDevTabletMode()) {
- cursor_setter_->HideCursor();
- return;
- }
- if (IsInCountDownAnimation()) {
- cursor_setter_->UpdateCursor(ui::mojom::CursorType::kPointer);
- return;
- }
- // If the current located event should be handled by camera preview, use the
- // pointer cursor.
- if (should_pass_located_event_to_camera_preview_) {
- cursor_setter_->UpdateCursor(ui::mojom::CursorType::kPointer);
- return;
- }
- // If the current mouse event is on capture label button, and capture label
- // button can handle the event, show the hand mouse cursor.
- const bool is_event_on_capture_button =
- capture_label_widget_->GetWindowBoundsInScreen().Contains(
- location_in_screen) &&
- static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView())
- ->ShouldHandleEvent();
- if (is_event_on_capture_button) {
- cursor_setter_->UpdateCursor(ui::mojom::CursorType::kHand);
- return;
- }
- // As long as the settings menu is open, a pointer cursor should be used as
- // long as the cursor is not on top of the capture button, since clicking
- // anywhere outside the bounds of either of them (the menu or the clickable
- // capture button) will dismiss the menu. Also if the event is on the bar, a
- // pointer will also be used, as long as the bar is visible.
- const bool is_event_on_capture_bar =
- capture_mode_bar_widget_->GetLayer()->GetTargetOpacity() &&
- capture_mode_bar_widget_->GetWindowBoundsInScreen().Contains(
- location_in_screen);
- if (capture_mode_settings_widget_ || is_event_on_capture_bar) {
- cursor_setter_->UpdateCursor(ui::mojom::CursorType::kPointer);
- return;
- }
- const CaptureModeSource source = controller_->source();
- if (source == CaptureModeSource::kWindow && !GetSelectedWindow()) {
- // If we're in window capture mode and there is no select window at the
- // moment, we should use a pointer cursor.
- cursor_setter_->UpdateCursor(ui::mojom::CursorType::kPointer);
- return;
- }
- if (source == CaptureModeSource::kFullscreen ||
- source == CaptureModeSource::kWindow) {
- // For fullscreen and other window capture cases, we should either use
- // image capture icon or screen record icon as the mouse icon.
- cursor_setter_->UpdateCursor(GetCursorForFullscreenOrWindowCapture(
- controller_->type() == CaptureModeType::kImage));
- return;
- }
- DCHECK_EQ(source, CaptureModeSource::kRegion);
- if (fine_tune_position_ != FineTunePosition::kNone) {
- // We're in fine tuning process.
- if (capture_mode_util::IsCornerFineTunePosition(fine_tune_position_)) {
- cursor_setter_->HideCursor();
- } else {
- cursor_setter_->UpdateCursor(
- GetCursorTypeForFineTunePosition(fine_tune_position_));
- }
- } else {
- // Otherwise update the cursor depending on the current cursor location.
- cursor_setter_->UpdateCursor(GetCursorTypeForFineTunePosition(
- GetFineTunePosition(location_in_screen, is_touch)));
- }
- }
- void CaptureModeSession::HighlightWindowForTab(aura::Window* window) {
- DCHECK(window);
- DCHECK_EQ(CaptureModeSource::kWindow, controller_->source());
- MaybeChangeRoot(window->GetRootWindow());
- capture_window_observer_->SetSelectedWindow(window);
- }
- void CaptureModeSession::MaybeUpdateSettingsBounds() {
- if (!capture_mode_settings_widget_)
- return;
- capture_mode_settings_widget_->SetBounds(CaptureModeSettingsView::GetBounds(
- capture_mode_bar_view_, capture_mode_settings_view_));
- }
- void CaptureModeSession::MaybeUpdateCaptureUisOpacity(
- absl::optional<gfx::Point> cursor_screen_location) {
- if (is_shutting_down_)
- return;
- // TODO(conniekxu): Handle this for tablet mode which doesn't have a cursor
- // screen point.
- if (!cursor_screen_location) {
- cursor_screen_location =
- display::Screen::GetScreen()->GetCursorScreenPoint();
- }
- base::flat_map<views::Widget*, /*opacity=*/float> widget_opacity_map;
- if (capture_mode_bar_widget_)
- widget_opacity_map[capture_mode_bar_widget_.get()] = 1.f;
- if (capture_label_widget_)
- widget_opacity_map[capture_label_widget_.get()] = 1.f;
- const bool is_settings_visible = capture_mode_settings_widget_ &&
- capture_mode_settings_widget_->IsVisible();
- gfx::Rect capture_region = controller_->user_capture_region();
- wm::ConvertRectToScreen(current_root_, &capture_region);
- for (auto& pair : widget_opacity_map) {
- views::Widget* widget = pair.first;
- float& opacity = pair.second;
- DCHECK(widget->GetLayer());
- const gfx::Rect window_bounds_in_screen = widget->GetWindowBoundsInScreen();
- const bool is_cursor_on_top_of_widget =
- window_bounds_in_screen.Contains(*cursor_screen_location);
- if (widget == capture_mode_bar_widget_.get()) {
- // If capture setting is visible, capture bar should be fully opaque even
- // if it's overlapped with camera preview.
- if (is_settings_visible)
- continue;
- // If drag for capture region is in progress, capture bar should be
- // hidden.
- if (is_drag_in_progress_) {
- opacity = 0.f;
- continue;
- }
- if (is_cursor_on_top_of_widget)
- continue;
- // If the cursor is hovering on top of the capture label, capture bar
- // should be fully opaque.
- if (capture_label_widget_ &&
- capture_label_widget_->GetWindowBoundsInScreen().Contains(
- *cursor_screen_location)) {
- continue;
- }
- const bool capture_bar_intersects_region =
- controller_->source() == CaptureModeSource::kRegion &&
- window_bounds_in_screen.Intersects(capture_region);
- if (capture_bar_intersects_region) {
- opacity = capture_mode::kCaptureUiOverlapOpacity;
- continue;
- }
- if (focus_cycler_->CaptureBarFocused())
- continue;
- }
- if (widget == capture_label_widget_.get() &&
- (is_cursor_on_top_of_widget || focus_cycler_->CaptureLabelFocused())) {
- continue;
- }
- if (IsWidgetOverlappedWithCameraPreview(widget))
- opacity = capture_mode::kCaptureUiOverlapOpacity;
- }
- for (const auto& pair : widget_opacity_map) {
- ui::Layer* layer = pair.first->GetLayer();
- const float& opacity = pair.second;
- if (layer->GetTargetOpacity() == opacity)
- continue;
- views::AnimationBuilder()
- .SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
- .Once()
- .SetDuration(kCaptureUIOpacityChangeDuration)
- .SetOpacity(layer, opacity, gfx::Tween::FAST_OUT_SLOW_IN);
- }
- }
- void CaptureModeSession::OnCameraPreviewDragStarted() {
- DCHECK(!controller_->is_recording_in_progress());
- // If settings menu is shown at the beginning of drag, we should close it.
- if (capture_mode_settings_widget_)
- SetSettingsMenuShown(false);
- // Hide capture UIs while dragging camera preview.
- HideAllUis();
- }
- void CaptureModeSession::OnCameraPreviewDragEnded(
- const gfx::Point& screen_location,
- bool is_touch) {
- // When drag for camera preview is ended, camera preview will be snapped to
- // one of the snap position, but cursor will leave at where the drag is
- // released. In order to update cursor type correctly after camera preview is
- // snapped, we should update `should_pass_located_event_to_camera_preview_` to
- // false if cursor is not on top of camera preview, since `UpdateCursor` will
- // rely on its value to decide whether cursor should be updated for camera
- // preview.
- auto* camera_preview_widget = GetCameraPreviewWidget();
- DCHECK(camera_preview_widget);
- if (!camera_preview_widget->GetWindowBoundsInScreen().Contains(
- screen_location)) {
- should_pass_located_event_to_camera_preview_ = false;
- }
- // If CaptureUIs (capture bar, capture label) are overlapped with camera
- // preview and cursor is not on top of it, its opacity should be updated to
- // `kCaptureUiOverlapOpacity` instead of fully opaque.
- MaybeUpdateCaptureUisOpacity(screen_location);
- // Show capture UIs which are hidden in `OnCameraPreviewDragStarted`.
- ShowAllUis();
- // Make sure cursor is updated correctly after camera preview is snapped.
- UpdateCursor(screen_location, is_touch);
- }
- void CaptureModeSession::OnCameraPreviewBoundsOrVisibilityChanged(
- bool capture_surface_became_too_small,
- bool did_bounds_or_visibility_change) {
- auto* camera_preview_widget = GetCameraPreviewWidget();
- DCHECK(camera_preview_widget);
- const bool is_parented_to_unparented_container =
- camera_preview_widget->GetNativeWindow()->parent()->GetId() ==
- kShellWindowId_UnparentedContainer;
- if (capture_surface_became_too_small && !is_drag_in_progress_ &&
- !is_parented_to_unparented_container) {
- // Since the user nudge toast has lower priority, if the toast for the
- // camera preview needs to be shown, user nudge toast should be dismissed
- // forever when applicable.
- MaybeDismissUserNudgeForever();
- capture_toast_controller_.ShowCaptureToast(
- CaptureToastType::kCameraPreview);
- } else {
- capture_toast_controller_.MaybeDismissCaptureToast(
- CaptureToastType::kCameraPreview);
- }
- if (did_bounds_or_visibility_change)
- MaybeUpdateCaptureUisOpacity();
- }
- void CaptureModeSession::OnCameraPreviewDestroyed() {
- capture_toast_controller_.MaybeDismissCaptureToast(
- CaptureToastType::kCameraPreview);
- }
- std::vector<views::Widget*> CaptureModeSession::GetAvailableWidgets() {
- std::vector<views::Widget*> result;
- DCHECK(capture_mode_bar_widget_);
- result.push_back(capture_mode_bar_widget_.get());
- if (capture_label_widget_)
- result.push_back(capture_label_widget_.get());
- if (capture_mode_settings_widget_)
- result.push_back(capture_mode_settings_widget_.get());
- if (dimensions_label_widget_)
- result.push_back(dimensions_label_widget_.get());
- if (auto* toast = capture_toast_controller_.capture_toast_widget())
- result.push_back(toast);
- return result;
- }
- void CaptureModeSession::HideAllUis() {
- is_all_uis_visible_ = false;
- cursor_setter_.reset();
- for (auto* widget : GetAvailableWidgets()) {
- // The order here matters. We need to disable the animation before we hide
- // to avoid any hide animation here, or until the widgets are shown (also
- // without animation) when ShowAllUis() is called.
- widget->GetNativeWindow()->SetProperty(aura::client::kAnimationsDisabledKey,
- true);
- // The layer's opacity could be less than 1.f if the widget was hidden
- // before we disabled the animations above. We need to reset the opacity
- // back to 1.f as we will hide the widget without animation.
- widget->GetLayer()->SetOpacity(1.f);
- widget->Hide();
- }
- // Refresh painting the layer, since we don't paint anything while a DLP
- // dialog might be shown.
- layer()->SchedulePaint(layer()->bounds());
- }
- void CaptureModeSession::ShowAllUis() {
- is_all_uis_visible_ = true;
- cursor_setter_ = std::make_unique<CursorSetter>();
- for (auto* widget : GetAvailableWidgets()) {
- // The order here matters. See HideAllUis() above.
- // At this point the animation is still disabled, so we show the window now
- // before we re-enable the animations. This is to avoid having those widgets
- // show up in the captured images or videos in case this is used right
- // before ending the session to perform the capture.
- if (CanShowWidget(widget))
- widget->Show();
- widget->GetNativeWindow()->SetProperty(aura::client::kAnimationsDisabledKey,
- false);
- }
- layer()->SchedulePaint(layer()->bounds());
- }
- bool CaptureModeSession::CanShowWidget(views::Widget* widget) const {
- // If `widget` is the toast widget, we shouldn't show it again in
- // `ShowAllUis()` unless there is an available toast type, and the toast was
- // never fully dismissed.
- if (widget == capture_toast_controller_.capture_toast_widget())
- return !!capture_toast_controller_.current_toast_type();
- // If widget is the capture label widget, we will show it only if it doesn't
- // intersect with the settings widget.
- return !(capture_label_widget_ && capture_mode_settings_widget_ &&
- capture_label_widget_.get() == widget &&
- capture_mode_settings_widget_->GetWindowBoundsInScreen().Intersects(
- capture_label_widget_->GetWindowBoundsInScreen()));
- }
- void CaptureModeSession::RefreshBarWidgetBounds() {
- DCHECK(capture_mode_bar_widget_);
- capture_mode_bar_widget_->SetBounds(
- CaptureModeBarView::GetBounds(current_root_, is_in_projector_mode_));
- auto* parent = GetParentContainer(current_root_);
- parent->StackChildAtTop(capture_mode_bar_widget_->GetNativeWindow());
- if (user_nudge_controller_)
- user_nudge_controller_->Reposition();
- capture_toast_controller_.MaybeRepositionCaptureToast();
- }
- void CaptureModeSession::MaybeCreateUserNudge() {
- user_nudge_controller_.reset();
- if (is_in_projector_mode_)
- return;
- if (!controller_->CanShowUserNudge())
- return;
- user_nudge_controller_ = std::make_unique<UserNudgeController>(
- this, capture_mode_bar_view_->settings_button());
- user_nudge_controller_->SetVisible(true);
- }
- void CaptureModeSession::MaybeDismissUserNudgeForever() {
- if (user_nudge_controller_)
- user_nudge_controller_->set_should_dismiss_nudge_forever(true);
- user_nudge_controller_.reset();
- }
- void CaptureModeSession::DoPerformCapture() {
- MaybeDismissUserNudgeForever();
- controller_->PerformCapture(); // `this` can be deleted after this.
- }
- gfx::Rect CaptureModeSession::GetSelectedWindowBounds() const {
- auto* window = GetSelectedWindow();
- return window ? window->bounds() : gfx::Rect();
- }
- void CaptureModeSession::RefreshStackingOrder() {
- if (is_shutting_down_)
- return;
- auto* parent_container = GetParentContainer(current_root_);
- DCHECK(parent_container);
- auto* session_layer = layer();
- auto* parent_container_layer = parent_container->layer();
- parent_container_layer->StackAtTop(session_layer);
- std::vector<views::Widget*> widget_in_order;
- auto* camera_preview_widget = GetCameraPreviewWidget();
- // We don't need to update the stacking order for camera preview if
- // there's a video recording in progress, since the camera preview don't
- // belong to the current capture session.
- if (camera_preview_widget && !controller_->is_recording_in_progress())
- widget_in_order.emplace_back(camera_preview_widget);
- if (auto* toast = capture_toast_controller_.capture_toast_widget())
- widget_in_order.emplace_back(toast);
- if (capture_label_widget_)
- widget_in_order.emplace_back(capture_label_widget_.get());
- if (capture_mode_bar_widget_)
- widget_in_order.emplace_back(capture_mode_bar_widget_.get());
- if (capture_mode_settings_widget_)
- widget_in_order.emplace_back(capture_mode_settings_widget_.get());
- for (auto* widget : widget_in_order) {
- auto* widget_window = widget->GetNativeWindow();
- // Make sure the order of `widget` layer and the order of `widget` window
- // match. Also notice we should stack layer later since when stacking
- // window, it will also stack window's layer which may mess the layer's
- // order if we stack layer first.
- if (widget_window->parent() == parent_container) {
- parent_container->StackChildAtTop(widget_window);
- parent_container_layer->StackAtTop(widget_window->layer());
- }
- }
- }
- void CaptureModeSession::PaintCaptureRegion(gfx::Canvas* canvas) {
- gfx::Rect region;
- bool adjustable_region = false;
- switch (controller_->source()) {
- case CaptureModeSource::kFullscreen:
- region = current_root_->bounds();
- break;
- case CaptureModeSource::kWindow:
- region = GetSelectedWindowBounds();
- break;
- case CaptureModeSource::kRegion:
- region = controller_->user_capture_region();
- adjustable_region = true;
- break;
- }
- if (region.IsEmpty())
- return;
- gfx::ScopedCanvas scoped_canvas(canvas);
- const float dsf = canvas->UndoDeviceScaleFactor();
- region = gfx::ScaleToEnclosingRect(region, dsf);
- if (!adjustable_region) {
- canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
- canvas->FillRect(
- region, AshColorProvider::Get()->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kCaptureRegionColor));
- return;
- }
- region.Inset(-capture_mode::kCaptureRegionBorderStrokePx);
- canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
- // Draw the region border.
- cc::PaintFlags border_flags;
- border_flags.setColor(capture_mode::kRegionBorderColor);
- border_flags.setStyle(cc::PaintFlags::kStroke_Style);
- border_flags.setStrokeWidth(capture_mode::kCaptureRegionBorderStrokePx);
- border_flags.setLooper(gfx::CreateShadowDrawLooper({kRegionOutlineShadow}));
- canvas->DrawRect(gfx::RectF(region), border_flags);
- // Draws the focus ring if the region or one of the affordance circles
- // currently has focus.
- auto maybe_draw_focus_ring = [&canvas, ®ion,
- &dsf](FineTunePosition position) {
- if (position == FineTunePosition::kNone)
- return;
- cc::PaintFlags focus_ring_flags;
- focus_ring_flags.setAntiAlias(true);
- focus_ring_flags.setColor(AshColorProvider::Get()->GetControlsLayerColor(
- AshColorProvider::ControlsLayerType::kFocusRingColor));
- focus_ring_flags.setStyle(cc::PaintFlags::kStroke_Style);
- focus_ring_flags.setStrokeWidth(kFocusRingStrokeWidthDp);
- if (position == FineTunePosition::kCenter) {
- gfx::RectF focus_rect(region);
- focus_rect.Inset(
- gfx::InsetsF(-kFocusRingSpacingDp - kFocusRingStrokeWidthDp / 2));
- canvas->DrawRect(focus_rect, focus_ring_flags);
- return;
- }
- const float radius =
- dsf * (kAffordanceCircleRadiusDp + kFocusRingSpacingDp +
- kFocusRingStrokeWidthDp / 2);
- canvas->DrawCircle(
- capture_mode_util::GetLocationForFineTunePosition(region, position),
- radius, focus_ring_flags);
- };
- const FineTunePosition focused_fine_tune_position =
- focus_cycler_->GetFocusedFineTunePosition();
- if (is_selecting_region_ || fine_tune_position_ != FineTunePosition::kNone) {
- maybe_draw_focus_ring(focused_fine_tune_position);
- return;
- }
- if (IsInCountDownAnimation())
- return;
- // Draw the drag affordance circles.
- cc::PaintFlags circle_flags;
- circle_flags.setColor(capture_mode::kRegionBorderColor);
- circle_flags.setStyle(cc::PaintFlags::kFill_Style);
- circle_flags.setAntiAlias(true);
- circle_flags.setLooper(gfx::CreateShadowDrawLooper(
- {kRegionAffordanceCircleShadow1, kRegionAffordanceCircleShadow2}));
- auto draw_circle = [&canvas, &circle_flags,
- &dsf](const gfx::Point& location) {
- canvas->DrawCircle(location, dsf * kAffordanceCircleRadiusDp, circle_flags);
- };
- draw_circle(region.origin());
- draw_circle(region.top_center());
- draw_circle(region.top_right());
- draw_circle(region.right_center());
- draw_circle(region.bottom_right());
- draw_circle(region.bottom_center());
- draw_circle(region.bottom_left());
- draw_circle(region.left_center());
- maybe_draw_focus_ring(focused_fine_tune_position);
- }
- void CaptureModeSession::OnLocatedEvent(ui::LocatedEvent* event,
- bool is_touch) {
- if (folder_selection_dialog_controller_) {
- if (folder_selection_dialog_controller_->ShouldConsumeEvent(event))
- event->StopPropagation();
- return;
- }
- // If we're currently in countdown animation, don't further handle any
- // located events. However we should stop the event propagation here to
- // prevent other event handlers from handling this event.
- if (IsInCountDownAnimation()) {
- event->StopPropagation();
- return;
- }
- // |ui::ET_MOUSE_EXITED| and |ui::ET_MOUSE_ENTERED| events will be generated
- // during moving capture mode bar to another display. We should ignore them
- // here, since they will overwrite the capture mode bar's root change during
- // keyboard tabbing in capture window mode.
- if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED ||
- event->type() == ui::ET_MOUSE_EXITED ||
- event->type() == ui::ET_MOUSE_ENTERED) {
- return;
- }
- // We should ignore synthesized events here. Otherwise, synthesized events
- // will overwrite the change by the actual event because of the
- // asynchronism (please check |WindowEventDispatcher::PostSynthesizeMouseMove|
- // for more information).
- // For example, during keyboard navigation in capture window mode, changing
- // root of capture mode bar will generate the synthesized mouse move event. It
- // will overwrite the root change since the location of the synthesized event
- // is still on the previous root.
- // For the window related synthesized events (window activation, window
- // destroy), |capture_window_observer_| can take care of them.
- if (event->flags() & ui::EF_IS_SYNTHESIZED)
- return;
- gfx::Point screen_location = event->location();
- aura::Window* event_target = static_cast<aura::Window*>(event->target());
- wm::ConvertPointToScreen(event_target, &screen_location);
- // For fullscreen/window mode, change the root window as soon as we detect the
- // cursor on a new display. For region mode, wait until the user taps down to
- // try to select a new region on the new display.
- const CaptureModeSource capture_source = controller_->source();
- const bool is_capture_region = capture_source == CaptureModeSource::kRegion;
- const bool is_press_event = event->type() == ui::ET_MOUSE_PRESSED ||
- event->type() == ui::ET_TOUCH_PRESSED;
- // Clear keyboard focus on presses.
- if (is_press_event && focus_cycler_->HasFocus())
- focus_cycler_->ClearFocus();
- const bool can_change_root = !is_capture_region || is_press_event;
- if (can_change_root)
- MaybeChangeRoot(GetPreferredRootWindow(screen_location));
- // The root may have switched while pressing the mouse down. Move the capture
- // bar to the current display if that is the case and make sure it is stacked
- // at the top. The dimensions label and capture button have been moved and
- // stacked on tap down so manually stack at top instead of calling
- // RefreshStackingOrder.
- const bool is_release_event = event->type() == ui::ET_MOUSE_RELEASED ||
- event->type() == ui::ET_TOUCH_RELEASED;
- if (is_release_event && is_capture_region &&
- current_root_ !=
- capture_mode_bar_widget_->GetNativeWindow()->GetRootWindow()) {
- RefreshBarWidgetBounds();
- }
- MaybeUpdateCaptureUisOpacity(screen_location);
- // Update the value of `should_pass_located_event_to_camera_preview_` here
- // before calling `UpdateCursor` which uses it.
- should_pass_located_event_to_camera_preview_ =
- ShouldPassEventToCameraPreview(event);
- // From here on, no matter where the function exists, the cursor must be
- // updated at the end.
- base::ScopedClosureRunner deferred_cursor_updater(base::BindOnce(
- &CaptureModeSession::UpdateCursor, weak_ptr_factory_.GetWeakPtr(),
- screen_location, is_touch));
- if (should_pass_located_event_to_camera_preview_) {
- DCHECK(!controller_->is_recording_in_progress());
- return;
- }
- // Let the capture button handle any events it can handle first.
- if (ShouldCaptureLabelHandleEvent(event_target))
- return;
- // Also allow events that target the settings menu (if present) to go through.
- if (IsEventTargetedOnSettingsMenu(*event))
- return;
- // Here we know that the event doesn't target the settings menu, so if it's a
- // press event, we will use it to dismiss the settings menu, unless it's on
- // the settings button (since in this case, the settings button handler will
- // take care of dismissing the menu).
- const bool is_event_on_settings_button =
- capture_mode_bar_view_->settings_button()->GetBoundsInScreen().Contains(
- screen_location);
- const bool should_close_settings = is_press_event &&
- !is_event_on_settings_button &&
- capture_mode_settings_widget_;
- if (should_close_settings) {
- // All future located events up to and including a released events will be
- // consumed and ignored (i.e. won't be used to update the capture region,
- // the selected window, or perform capture ... etc.), unless it's targeting
- // the capture button.
- ignore_located_events_ = true;
- SetSettingsMenuShown(/*shown=*/false);
- }
- const bool old_ignore_located_events = ignore_located_events_;
- if (ignore_located_events_) {
- if (is_release_event)
- ignore_located_events_ = false;
- }
- // Events targeting the capture bar should also go through.
- if (IsEventTargetedOnCaptureBar(*event))
- return;
- event->SetHandled();
- event->StopPropagation();
- if (should_close_settings || old_ignore_located_events) {
- // Note that these ignored events have already been consumed above.
- return;
- }
- const bool is_capture_fullscreen =
- capture_source == CaptureModeSource::kFullscreen;
- const bool is_capture_window = capture_source == CaptureModeSource::kWindow;
- if (is_capture_fullscreen || is_capture_window) {
- switch (event->type()) {
- case ui::ET_MOUSE_MOVED:
- case ui::ET_TOUCH_PRESSED:
- case ui::ET_TOUCH_MOVED: {
- if (is_capture_window) {
- capture_window_observer_->UpdateSelectedWindowAtPosition(
- screen_location, {});
- }
- break;
- }
- case ui::ET_MOUSE_RELEASED:
- case ui::ET_TOUCH_RELEASED:
- if (is_capture_fullscreen ||
- (is_capture_window && GetSelectedWindow())) {
- DoPerformCapture(); // `this` can be deleted after this.
- }
- break;
- default:
- break;
- }
- return;
- }
- DCHECK(is_capture_region);
- // `OnLocatedEventPressed()` and `OnLocatedEventDragged()` used root locations
- // since `CaptureModeController::user_capture_region()` is stored in root
- // coordinates.
- const gfx::Point& location_in_root = event->root_location();
- switch (event->type()) {
- case ui::ET_MOUSE_PRESSED:
- case ui::ET_TOUCH_PRESSED:
- old_mouse_warp_status_ = SetMouseWarpEnabled(false);
- OnLocatedEventPressed(location_in_root, is_touch);
- break;
- case ui::ET_MOUSE_DRAGGED:
- case ui::ET_TOUCH_MOVED:
- OnLocatedEventDragged(location_in_root);
- break;
- case ui::ET_MOUSE_RELEASED:
- case ui::ET_TOUCH_RELEASED:
- // Reenable mouse warping.
- if (old_mouse_warp_status_)
- SetMouseWarpEnabled(*old_mouse_warp_status_);
- old_mouse_warp_status_.reset();
- OnLocatedEventReleased(location_in_root);
- break;
- default:
- break;
- }
- }
- FineTunePosition CaptureModeSession::GetFineTunePosition(
- const gfx::Point& location_in_screen,
- bool is_touch) const {
- // When the region is empty, this is a brand new selection rather than a fine
- // tune.
- if (controller_->user_capture_region().IsEmpty())
- return FineTunePosition::kNone;
- gfx::Rect capture_region_in_screen = controller_->user_capture_region();
- wm::ConvertRectToScreen(current_root_, &capture_region_in_screen);
- // In the case of overlapping affordances, prioritize the bottomm right
- // corner, then the rest of the corners, then the edges.
- static const std::vector<FineTunePosition> drag_positions = {
- FineTunePosition::kBottomRight, FineTunePosition::kBottomLeft,
- FineTunePosition::kTopLeft, FineTunePosition::kTopRight,
- FineTunePosition::kBottomCenter, FineTunePosition::kLeftCenter,
- FineTunePosition::kTopCenter, FineTunePosition::kRightCenter};
- const int hit_radius =
- is_touch ? kAffordanceCircleTouchHitRadiusDp : kAffordanceCircleRadiusDp;
- const int hit_radius_squared = hit_radius * hit_radius;
- for (FineTunePosition position : drag_positions) {
- const gfx::Point position_location =
- capture_mode_util::GetLocationForFineTunePosition(
- capture_region_in_screen, position);
- // If `location_in_screen` is within `hit_radius` of `position_location` for
- // both x and y, then `position` is the current pressed down affordance.
- if ((position_location - location_in_screen).LengthSquared() <=
- hit_radius_squared) {
- return position;
- }
- }
- if (capture_region_in_screen.Contains(location_in_screen))
- return FineTunePosition::kCenter;
- return FineTunePosition::kNone;
- }
- void CaptureModeSession::OnLocatedEventPressed(
- const gfx::Point& location_in_root,
- bool is_touch) {
- initial_location_in_root_ = location_in_root;
- previous_location_in_root_ = location_in_root;
- // Use cursor compositing instead of the platform cursor when dragging to
- // ensure the cursor is aligned with the region.
- is_drag_in_progress_ = true;
- Shell::Get()->UpdateCursorCompositingEnabled();
- if (user_nudge_controller_)
- user_nudge_controller_->SetVisible(false);
- gfx::Point screen_location = location_in_root;
- wm::ConvertPointToScreen(current_root_, &screen_location);
- MaybeUpdateCaptureUisOpacity(screen_location);
- // Run `MaybeUpdateCameraPreviewBounds` at the exit of this function's
- // scope since the camera preview should be hidden if user is dragging to
- // update the capture region. The reason we want to run it at the exit of this
- // function is if `is_selecting_region_` is false, we want
- // `fine_tune_position_` to be updated first since it can affect whether we
- // should hide camera preview or not.
- base::ScopedClosureRunner deferred_runner(
- base::BindOnce(&CaptureModeSession::MaybeUpdateCameraPreviewBounds,
- weak_ptr_factory_.GetWeakPtr()));
- if (is_selecting_region_)
- return;
- fine_tune_position_ = GetFineTunePosition(screen_location, is_touch);
- if (fine_tune_position_ == FineTunePosition::kNone) {
- // If the point is outside the capture region and not on the capture bar or
- // settings menu, restart to the select phase.
- is_selecting_region_ = true;
- UpdateCaptureRegion(gfx::Rect(), /*is_resizing=*/true, /*by_user=*/true);
- num_capture_region_adjusted_ = 0;
- return;
- }
- // In order to hide the drag affordance circles on click, we need to repaint
- // the capture region.
- if (fine_tune_position_ != FineTunePosition::kNone) {
- ++num_capture_region_adjusted_;
- RepaintRegion();
- }
- if (fine_tune_position_ != FineTunePosition::kCenter &&
- fine_tune_position_ != FineTunePosition::kNone) {
- anchor_points_ = GetAnchorPointsForPosition(fine_tune_position_);
- const gfx::Point position_location =
- capture_mode_util::GetLocationForFineTunePosition(
- controller_->user_capture_region(), fine_tune_position_);
- MaybeShowMagnifierGlassAtPoint(position_location);
- }
- }
- void CaptureModeSession::OnLocatedEventDragged(
- const gfx::Point& location_in_root) {
- const gfx::Point previous_location_in_root = previous_location_in_root_;
- previous_location_in_root_ = location_in_root;
- // For the select phase, the select region is the rectangle formed by the
- // press location and the current location.
- if (is_selecting_region_) {
- UpdateCaptureRegion(
- GetRectEnclosingPoints({initial_location_in_root_, location_in_root}),
- /*is_resizing=*/true, /*by_user=*/true);
- return;
- }
- if (fine_tune_position_ == FineTunePosition::kNone)
- return;
- // For a reposition, offset the old select region by the difference between
- // the current location and the previous location, but do not let the select
- // region go offscreen.
- if (fine_tune_position_ == FineTunePosition::kCenter) {
- gfx::Rect new_capture_region = controller_->user_capture_region();
- new_capture_region.Offset(location_in_root - previous_location_in_root);
- new_capture_region.AdjustToFit(current_root_->bounds());
- UpdateCaptureRegion(new_capture_region, /*is_resizing=*/false,
- /*by_user=*/true);
- return;
- }
- // The new region is defined by the rectangle which encloses the anchor
- // point(s) and |resizing_point|, which is based off of |location_in_root| but
- // prevents edge drags from resizing the region in the non-desired direction.
- std::vector<gfx::Point> points = anchor_points_;
- DCHECK(!points.empty());
- gfx::Point resizing_point = location_in_root;
- // For edge dragging, there will be two anchor points with the same primary
- // axis value. Setting |resizing_point|'s secondary axis value to match either
- // one of the anchor points secondary axis value will ensure that for the
- // duration of a drag, GetRectEnclosingPoints will return a rect whose
- // secondary dimension does not change.
- if (fine_tune_position_ == FineTunePosition::kLeftCenter ||
- fine_tune_position_ == FineTunePosition::kRightCenter) {
- resizing_point.set_y(points.front().y());
- } else if (fine_tune_position_ == FineTunePosition::kTopCenter ||
- fine_tune_position_ == FineTunePosition::kBottomCenter) {
- resizing_point.set_x(points.front().x());
- }
- points.push_back(resizing_point);
- UpdateCaptureRegion(GetRectEnclosingPoints(points), /*is_resizing=*/true,
- /*by_user=*/true);
- MaybeShowMagnifierGlassAtPoint(location_in_root);
- }
- void CaptureModeSession::OnLocatedEventReleased(
- const gfx::Point& location_in_root) {
- // TODO(conniekxu): Handle the opacity of the toast widget when it's
- // overlapped with the capture region.
- if (user_nudge_controller_)
- user_nudge_controller_->SetVisible(true);
- gfx::Point screen_location = location_in_root;
- wm::ConvertPointToScreen(current_root_, &screen_location);
- EndSelection(screen_location);
- // Do a repaint to show the affordance circles.
- RepaintRegion();
- // Run `MaybeUpdateCameraPreviewBounds` when user releases the drag at
- // the exit of this function's scope to show the camera preview which may have
- // been hidden in `OnLocatedEventPressed`. The reason we want to run it at the
- // exit of this function is if `is_selecting_region_` is true, we want to wait
- // until the capture label is updated since capture label's opacity may need
- // to be updated based on if it's overlapped with camera preview or not.
- base::ScopedClosureRunner deferred_runner(
- base::BindOnce(&CaptureModeSession::MaybeUpdateCameraPreviewBounds,
- weak_ptr_factory_.GetWeakPtr()));
- if (!is_selecting_region_)
- return;
- // After first release event, we advance to the next phase.
- is_selecting_region_ = false;
- UpdateCaptureLabelWidget(CaptureLabelAnimation::kRegionPhaseChange);
- A11yAlertCaptureSource(/*trigger_now=*/true);
- }
- void CaptureModeSession::UpdateCaptureRegion(
- const gfx::Rect& new_capture_region,
- bool is_resizing,
- bool by_user) {
- const gfx::Rect old_capture_region = controller_->user_capture_region();
- if (old_capture_region == new_capture_region)
- return;
- // Calculate the region that has been damaged and repaint the layer. Add some
- // extra padding to make sure the border and affordance circles are also
- // repainted.
- gfx::Rect damage_region = old_capture_region;
- damage_region.Union(new_capture_region);
- damage_region.Inset(gfx::Insets(-kDamageInsetDp));
- layer()->SchedulePaint(damage_region);
- controller_->SetUserCaptureRegion(new_capture_region, by_user);
- UpdateDimensionsLabelWidget(is_resizing);
- UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
- }
- void CaptureModeSession::UpdateDimensionsLabelWidget(bool is_resizing) {
- const bool should_not_show =
- !is_resizing || controller_->source() != CaptureModeSource::kRegion ||
- controller_->user_capture_region().IsEmpty();
- if (should_not_show) {
- dimensions_label_widget_.reset();
- return;
- }
- if (!dimensions_label_widget_) {
- auto* parent = GetParentContainer(current_root_);
- dimensions_label_widget_ = std::make_unique<views::Widget>();
- dimensions_label_widget_->Init(
- CreateWidgetParams(parent, gfx::Rect(), "CaptureModeDimensionsLabel"));
- auto size_label = std::make_unique<views::Label>();
- auto* color_provider = AshColorProvider::Get();
- size_label->SetEnabledColor(color_provider->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kTextColorPrimary));
- size_label->SetBackground(views::CreateRoundedRectBackground(
- color_provider->GetBaseLayerColor(
- AshColorProvider::BaseLayerType::kTransparent80),
- kSizeLabelBorderRadius));
- size_label->SetAutoColorReadabilityEnabled(false);
- dimensions_label_widget_->SetContentsView(std::move(size_label));
- dimensions_label_widget_->Show();
- // When moving to a new display, the dimensions label gets created/moved
- // onto the new display on press, while the capture bar gets moved on
- // release. In this case, we do not have to stack the dimensions label.
- if (parent == capture_mode_bar_widget_->GetNativeWindow()->parent()) {
- parent->StackChildBelow(dimensions_label_widget_->GetNativeWindow(),
- capture_mode_bar_widget_->GetNativeWindow());
- }
- }
- views::Label* size_label =
- static_cast<views::Label*>(dimensions_label_widget_->GetContentsView());
- const gfx::Rect capture_region = controller_->user_capture_region();
- size_label->SetText(base::UTF8ToUTF16(base::StringPrintf(
- "%d x %d", capture_region.width(), capture_region.height())));
- UpdateDimensionsLabelBounds();
- }
- void CaptureModeSession::UpdateDimensionsLabelBounds() {
- DCHECK(dimensions_label_widget_ &&
- dimensions_label_widget_->GetContentsView());
- gfx::Rect bounds(
- dimensions_label_widget_->GetContentsView()->GetPreferredSize());
- const gfx::Rect capture_region = controller_->user_capture_region();
- gfx::Rect screen_region = current_root_->bounds();
- bounds.set_width(bounds.width() + 2 * kSizeLabelHorizontalPadding);
- bounds.set_x(capture_region.CenterPoint().x() - bounds.width() / 2);
- bounds.set_y(capture_region.bottom() + kSizeLabelYDistanceFromRegionDp);
- // The dimension label should always be within the screen and at the bottom of
- // the capture region. If it does not fit below the bottom edge fo the region,
- // move it above the bottom edge into the capture region.
- screen_region.Inset(
- gfx::Insets::TLBR(0, 0, kSizeLabelYDistanceFromRegionDp, 0));
- bounds.AdjustToFit(screen_region);
- wm::ConvertRectToScreen(current_root_, &bounds);
- dimensions_label_widget_->SetBounds(bounds);
- }
- void CaptureModeSession::MaybeShowMagnifierGlassAtPoint(
- const gfx::Point& location_in_root) {
- if (!capture_mode_util::IsCornerFineTunePosition(fine_tune_position_))
- return;
- magnifier_glass_.ShowFor(current_root_, location_in_root);
- }
- void CaptureModeSession::CloseMagnifierGlass() {
- magnifier_glass_.Close();
- }
- std::vector<gfx::Point> CaptureModeSession::GetAnchorPointsForPosition(
- FineTunePosition position) {
- std::vector<gfx::Point> anchor_points;
- // For a vertex, the anchor point is the opposite vertex on the rectangle
- // (ex. bottom left vertex -> top right vertex anchor point). For an edge, the
- // anchor points are the two vertices of the opposite edge (ex. bottom edge ->
- // top left and top right anchor points).
- const gfx::Rect rect = controller_->user_capture_region();
- switch (position) {
- case FineTunePosition::kNone:
- case FineTunePosition::kCenter:
- break;
- case FineTunePosition::kTopLeft:
- anchor_points.push_back(rect.bottom_right());
- break;
- case FineTunePosition::kTopCenter:
- anchor_points.push_back(rect.bottom_left());
- anchor_points.push_back(rect.bottom_right());
- break;
- case FineTunePosition::kTopRight:
- anchor_points.push_back(rect.bottom_left());
- break;
- case FineTunePosition::kLeftCenter:
- anchor_points.push_back(rect.top_right());
- anchor_points.push_back(rect.bottom_right());
- break;
- case FineTunePosition::kRightCenter:
- anchor_points.push_back(rect.origin());
- anchor_points.push_back(rect.bottom_left());
- break;
- case FineTunePosition::kBottomLeft:
- anchor_points.push_back(rect.top_right());
- break;
- case FineTunePosition::kBottomCenter:
- anchor_points.push_back(rect.origin());
- anchor_points.push_back(rect.top_right());
- break;
- case FineTunePosition::kBottomRight:
- anchor_points.push_back(rect.origin());
- break;
- }
- DCHECK(!anchor_points.empty());
- DCHECK_LE(anchor_points.size(), 2u);
- return anchor_points;
- }
- void CaptureModeSession::UpdateCaptureLabelWidget(
- CaptureLabelAnimation animation_type) {
- if (!capture_label_widget_) {
- capture_label_widget_ = std::make_unique<views::Widget>();
- auto* parent = GetParentContainer(current_root_);
- capture_label_widget_->Init(
- CreateWidgetParams(parent, gfx::Rect(), "CaptureLabel"));
- capture_label_widget_->SetContentsView(std::make_unique<CaptureLabelView>(
- this, base::BindRepeating(&CaptureModeSession::DoPerformCapture,
- base::Unretained(this))));
- capture_label_widget_->GetNativeWindow()->SetTitle(
- l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_A11Y_TITLE));
- capture_label_widget_->Show();
- }
- CaptureLabelView* label_view =
- static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
- label_view->UpdateIconAndText();
- UpdateCaptureLabelWidgetBounds(animation_type);
- focus_cycler_->OnCaptureLabelWidgetUpdated();
- }
- void CaptureModeSession::UpdateCaptureLabelWidgetBounds(
- CaptureLabelAnimation animation_type) {
- DCHECK(capture_label_widget_);
- const gfx::Rect bounds = CalculateCaptureLabelWidgetBounds();
- const gfx::Rect old_bounds =
- capture_label_widget_->GetNativeWindow()->GetBoundsInScreen();
- if (old_bounds == bounds)
- return;
- if (animation_type == CaptureLabelAnimation::kNone) {
- capture_label_widget_->SetBounds(bounds);
- return;
- }
- ui::Layer* layer = capture_label_widget_->GetLayer();
- ui::LayerAnimator* animator = layer->GetAnimator();
- if (animation_type == CaptureLabelAnimation::kRegionPhaseChange) {
- capture_label_widget_->SetBounds(bounds);
- const gfx::Point center_point = bounds.CenterPoint();
- layer->SetTransform(
- gfx::GetScaleTransform(gfx::Point(center_point.x() - bounds.x(),
- center_point.y() - bounds.y()),
- kLabelScaleDownOnPhaseChange));
- layer->SetOpacity(0.f);
- ui::ScopedLayerAnimationSettings settings(animator);
- settings.SetTransitionDuration(kCaptureLabelRegionPhaseChangeDuration);
- settings.SetTweenType(gfx::Tween::ACCEL_LIN_DECEL_100);
- settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
- animator->SchedulePauseForProperties(
- kCaptureLabelRegionPhaseChangeDelay,
- ui::LayerAnimationElement::TRANSFORM |
- ui::LayerAnimationElement::OPACITY);
- layer->SetTransform(gfx::Transform());
- layer->SetOpacity(1.f);
- return;
- }
- DCHECK_EQ(CaptureLabelAnimation::kCountdownStart, animation_type);
- if (!old_bounds.IsEmpty()) {
- // This happens if there is a label or a label button showing when count
- // down starts. In this case we'll do a bounds change animation.
- ui::ScopedLayerAnimationSettings settings(animator);
- settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
- settings.SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
- settings.SetTransitionDuration(kCaptureLabelCountdownStartDuration);
- capture_label_widget_->SetBounds(bounds);
- } else {
- // This happens when no text message was showing when count down starts, in
- // this case we'll do a fade in + shrinking down animation.
- capture_label_widget_->SetBounds(bounds);
- const gfx::Point center_point = bounds.CenterPoint();
- layer->SetTransform(
- gfx::GetScaleTransform(gfx::Point(center_point.x() - bounds.x(),
- center_point.y() - bounds.y()),
- kLabelScaleUpOnCountdown));
- layer->SetOpacity(0.f);
- // Fade in.
- ui::ScopedLayerAnimationSettings settings(animator);
- settings.SetTransitionDuration(kCaptureLabelCountdownStartDuration);
- settings.SetTweenType(gfx::Tween::LINEAR);
- settings.SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
- layer->SetOpacity(1.f);
- // Scale down from 120% -> 100%.
- settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
- layer->SetTransform(gfx::Transform());
- }
- }
- gfx::Rect CaptureModeSession::CalculateCaptureLabelWidgetBounds() {
- DCHECK(capture_label_widget_);
- CaptureLabelView* label_view =
- static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
- const gfx::Size preferred_size = label_view->GetPreferredSize();
- const gfx::Rect capture_bar_bounds =
- capture_mode_bar_widget_->GetNativeWindow()->bounds();
- // Calculates the bounds for when the capture label is not placed in the
- // middle of the screen.
- auto calculate_bounds = [&preferred_size, &capture_bar_bounds](
- const gfx::Rect& capture_bounds,
- aura::Window* root) {
- // The capture_bounds must be at least the size of |preferred_size| plus
- // some padding for the capture label to be centered inside it.
- gfx::Rect label_bounds(capture_bounds);
- gfx::Size capture_bounds_min_size = preferred_size;
- capture_bounds_min_size.Enlarge(kCaptureRegionMinimumPaddingDp,
- kCaptureRegionMinimumPaddingDp);
- // If the label fits into |capture_bounds| with a comfortable padding, and
- // does not intersect the capture bar, we're good.
- if (label_bounds.width() > capture_bounds_min_size.width() &&
- label_bounds.height() > capture_bounds_min_size.height()) {
- label_bounds.ClampToCenteredSize(preferred_size);
- if (!label_bounds.Intersects(capture_bar_bounds))
- return label_bounds;
- }
- // The capture button may be placed along the edge of a capture region if it
- // cannot be placed in the middle. This enum represents the possible edges.
- enum class Direction { kBottom, kTop, kLeft, kRight };
- // Try placing the label slightly outside |capture_bounds|. The label will
- // be |kCaptureButtonDistanceFromRegionDp| away from |capture_bounds| along
- // one of the edges. The order we will try is bottom, top, left then right.
- const std::vector<Direction> directions = {
- Direction::kBottom, Direction::kTop, Direction::kLeft,
- Direction::kRight};
- // For each direction, start off with the label in the center of
- // |capture_bounds| (matching centerpoints). We will shift the label to
- // slighty outside |capture_bounds| for each direction.
- gfx::Rect centered_label_bounds(preferred_size);
- centered_label_bounds.set_x(capture_bounds.CenterPoint().x() -
- preferred_size.width() / 2);
- centered_label_bounds.set_y(capture_bounds.CenterPoint().y() -
- preferred_size.height() / 2);
- const int spacing = kCaptureButtonDistanceFromRegionDp;
- // Try the directions in the preferred order. We will early out if one of
- // them is viable.
- for (Direction direction : directions) {
- label_bounds = centered_label_bounds;
- switch (direction) {
- case Direction::kBottom:
- label_bounds.set_y(capture_bounds.bottom() + spacing);
- break;
- case Direction::kTop:
- label_bounds.set_y(capture_bounds.y() - spacing -
- preferred_size.height());
- break;
- case Direction::kLeft:
- label_bounds.set_x(capture_bounds.x() - spacing -
- preferred_size.width());
- break;
- case Direction::kRight:
- label_bounds.set_x(capture_bounds.right() + spacing);
- break;
- }
- // If |label_bounds| does not overlap with |capture_bar_bounds| and is
- // fully contained in root, we're good.
- if (!label_bounds.Intersects(capture_bar_bounds) &&
- root->bounds().Contains(label_bounds)) {
- return label_bounds;
- }
- }
- // Reaching here, we have not found a good edge to place the label at. The
- // last attempt is to place it slightly above the capture bar.
- label_bounds.set_size(preferred_size);
- label_bounds.set_x(capture_bar_bounds.CenterPoint().x() -
- preferred_size.width() / 2);
- label_bounds.set_y(capture_bar_bounds.y() -
- kCaptureButtonDistanceFromRegionDp -
- preferred_size.height());
- return label_bounds;
- };
- gfx::Rect bounds(current_root_->bounds());
- const gfx::Rect capture_region = controller_->user_capture_region();
- const gfx::Rect window_bounds = GetSelectedWindowBounds();
- const CaptureModeSource source = controller_->source();
- // For fullscreen mode, the capture label is placed in the middle of the
- // screen. For region capture mode, if it's in select phase, the capture label
- // is also placed in the middle of the screen, and if it's in fine tune phase,
- // the capture label is ideally placed in the middle of the capture region. If
- // it cannot fit, then it will be placed slightly above or below the capture
- // region. For window capture mode, it is the same as the region capture mode
- // fine tune phase logic, in that it will first try to place the label in the
- // middle of the selected window bounds, otherwise it will be placed slightly
- // away from one of the edges of the selected window.
- if (source == CaptureModeSource::kRegion && !is_selecting_region_ &&
- !capture_region.IsEmpty()) {
- if (label_view->IsInCountDownAnimation()) {
- // If countdown starts, calculate the bounds based on the old capture
- // label's position, otherwise, since the countdown label bounds is
- // smaller than the label bounds and may fit into the capture region even
- // if the old capture label doesn't fit thus was place outside of the
- // capture region, it's possible that we see the countdown label animates
- // to inside of the capture region from outside of the capture region.
- bounds = capture_label_widget_->GetNativeWindow()->bounds();
- bounds.ClampToCenteredSize(preferred_size);
- } else {
- bounds = calculate_bounds(capture_region, current_root_);
- }
- } else if (source == CaptureModeSource::kWindow && !window_bounds.IsEmpty()) {
- bounds = calculate_bounds(window_bounds, current_root_);
- } else {
- bounds.ClampToCenteredSize(preferred_size);
- }
- // User capture bounds are in root window coordinates so convert them here.
- wm::ConvertRectToScreen(current_root_, &bounds);
- return bounds;
- }
- bool CaptureModeSession::ShouldCaptureLabelHandleEvent(
- aura::Window* event_target) {
- if (!capture_label_widget_ ||
- capture_label_widget_->GetNativeWindow() != event_target) {
- return false;
- }
- CaptureLabelView* label_view =
- static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
- return label_view->ShouldHandleEvent();
- }
- void CaptureModeSession::MaybeChangeRoot(aura::Window* new_root) {
- DCHECK(new_root->IsRootWindow());
- if (new_root == current_root_)
- return;
- auto* new_parent = GetParentContainer(new_root);
- parent_container_observer_ =
- std::make_unique<ParentContainerObserver>(new_parent, this);
- new_parent->layer()->Add(layer());
- layer()->SetBounds(new_parent->bounds());
- current_root_ = new_root;
- // Update the bounds of the widgets after setting the new root. For region
- // capture, the capture bar will move at a later time, when the mouse is
- // released.
- if (controller_->source() != CaptureModeSource::kRegion)
- RefreshBarWidgetBounds();
- // Because we use custom cursors for region and full screen capture, we need
- // to update the cursor in case the display DSF changes.
- UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
- /*is_touch=*/false);
- // The following call to UpdateCaptureRegion will update the capture label
- // bounds, moving it onto the correct display, but will early return if the
- // region is already empty.
- if (controller_->user_capture_region().IsEmpty())
- UpdateCaptureLabelWidgetBounds(CaptureLabelAnimation::kNone);
- // Start with a new region when we switch displays.
- is_selecting_region_ = true;
- UpdateCaptureRegion(gfx::Rect(), /*is_resizing=*/false, /*by_user=*/false);
- UpdateRootWindowDimmers();
- MaybeReparentCameraPreviewWidget();
- }
- void CaptureModeSession::UpdateRootWindowDimmers() {
- root_window_dimmers_.clear();
- // Add dimmers for all root windows except |current_root_| if needed.
- for (aura::Window* root_window : Shell::GetAllRootWindows()) {
- if (root_window == current_root_)
- continue;
- auto dimmer = std::make_unique<WindowDimmer>(root_window);
- dimmer->window()->Show();
- root_window_dimmers_.emplace(std::move(dimmer));
- }
- }
- bool CaptureModeSession::IsUsingCustomCursor(CaptureModeType type) const {
- return cursor_setter_->IsUsingCustomCursor(type);
- }
- void CaptureModeSession::ClampCaptureRegionToRootWindowSize() {
- gfx::Rect new_capture_region = controller_->user_capture_region();
- new_capture_region.AdjustToFit(current_root_->bounds());
- controller_->SetUserCaptureRegion(new_capture_region, /*by_user=*/false);
- }
- void CaptureModeSession::EndSelection(
- absl::optional<gfx::Point> cursor_screen_location) {
- fine_tune_position_ = FineTunePosition::kNone;
- anchor_points_.clear();
- is_drag_in_progress_ = false;
- Shell::Get()->UpdateCursorCompositingEnabled();
- MaybeUpdateCaptureUisOpacity(cursor_screen_location);
- UpdateDimensionsLabelWidget(/*is_resizing=*/false);
- CloseMagnifierGlass();
- }
- void CaptureModeSession::RepaintRegion() {
- gfx::Rect damage_region = controller_->user_capture_region();
- damage_region.Inset(gfx::Insets(-kDamageInsetDp));
- layer()->SchedulePaint(damage_region);
- }
- void CaptureModeSession::SelectDefaultRegion() {
- is_selecting_region_ = false;
- // Default is centered in the root, and its width and height are
- // |kRegionDefaultRatio| size of the root.
- gfx::Rect default_capture_region = current_root_->bounds();
- default_capture_region.ClampToCenteredSize(gfx::ScaleToCeiledSize(
- default_capture_region.size(), kRegionDefaultRatio));
- UpdateCaptureRegion(default_capture_region, /*is_resizing=*/false,
- /*by_user=*/true);
- }
- void CaptureModeSession::UpdateRegionHorizontally(bool left, int event_flags) {
- const FineTunePosition focused_fine_tune_position =
- focus_cycler_->GetFocusedFineTunePosition();
- if (focused_fine_tune_position == FineTunePosition::kNone ||
- focused_fine_tune_position == FineTunePosition::kTopCenter ||
- focused_fine_tune_position == FineTunePosition::kBottomCenter) {
- return;
- }
- const int change = GetArrowKeyPressChange(event_flags);
- gfx::Rect new_capture_region = controller_->user_capture_region();
- if (focused_fine_tune_position == FineTunePosition::kCenter) {
- new_capture_region.Offset(left ? -change : change, 0);
- new_capture_region.AdjustToFit(current_root_->bounds());
- } else {
- const gfx::Point location =
- capture_mode_util::GetLocationForFineTunePosition(
- new_capture_region, focused_fine_tune_position);
- // If an affordance circle on the left side of the capture region is
- // focused, left presses will enlarge the existing region and right presses
- // will shrink the existing region. If it is on the right side, right
- // presses will enlarge and left presses will shrink.
- const bool affordance_on_left = location.x() == new_capture_region.x();
- const bool shrink = affordance_on_left ^ left;
- if (shrink && new_capture_region.width() < change)
- return;
- const int inset = shrink ? change : -change;
- auto insets = gfx::Insets::TLBR(0, affordance_on_left ? inset : 0, 0,
- affordance_on_left ? 0 : inset);
- new_capture_region.Inset(insets);
- ClipRectToFit(&new_capture_region, current_root_->bounds());
- }
- UpdateCaptureRegion(new_capture_region, /*is_resizing=*/false,
- /*by_user=*/true);
- }
- void CaptureModeSession::UpdateRegionVertically(bool up, int event_flags) {
- const FineTunePosition focused_fine_tune_position =
- focus_cycler_->GetFocusedFineTunePosition();
- if (focused_fine_tune_position == FineTunePosition::kNone ||
- focused_fine_tune_position == FineTunePosition::kLeftCenter ||
- focused_fine_tune_position == FineTunePosition::kRightCenter) {
- return;
- }
- const int change = GetArrowKeyPressChange(event_flags);
- gfx::Rect new_capture_region = controller_->user_capture_region();
- // TODO(sammiequon): The below is similar to UpdateRegionHorizontally() except
- // we are acting on the y-axis. Investigate if we can remove the duplication.
- if (focused_fine_tune_position == FineTunePosition::kCenter) {
- new_capture_region.Offset(0, up ? -change : change);
- new_capture_region.AdjustToFit(current_root_->bounds());
- } else {
- const gfx::Point location =
- capture_mode_util::GetLocationForFineTunePosition(
- new_capture_region, focused_fine_tune_position);
- // If an affordance circle on the top side of the capture region is
- // focused, up presses will enlarge the existing region and down presses
- // will shrink the existing region. If it is on the bottom side, down
- // presses will enlarge and up presses will shrink.
- const bool affordance_on_top = location.y() == new_capture_region.y();
- const bool shrink = affordance_on_top ^ up;
- if (shrink && new_capture_region.height() < change)
- return;
- const int inset = shrink ? change : -change;
- auto insets = gfx::Insets::TLBR(affordance_on_top ? inset : 0, 0,
- affordance_on_top ? 0 : inset, 0);
- new_capture_region.Inset(insets);
- ClipRectToFit(&new_capture_region, current_root_->bounds());
- }
- UpdateCaptureRegion(new_capture_region, /*is_resizing=*/false,
- /*by_user=*/true);
- }
- void CaptureModeSession::MaybeReparentCameraPreviewWidget() {
- if (!controller_->is_recording_in_progress())
- controller_->camera_controller()->MaybeReparentPreviewWidget();
- }
- void CaptureModeSession::MaybeUpdateCameraPreviewBounds() {
- if (!controller_->is_recording_in_progress()) {
- controller_->camera_controller()->MaybeUpdatePreviewWidget(
- /*animate=*/false);
- }
- }
- bool CaptureModeSession::IsEventTargetedOnCaptureBar(
- const ui::LocatedEvent& event) const {
- DCHECK(capture_mode_bar_widget_);
- auto* target = static_cast<aura::Window*>(event.target());
- return capture_mode_bar_widget_->GetNativeWindow()->Contains(target);
- }
- bool CaptureModeSession::IsEventTargetedOnSettingsMenu(
- const ui::LocatedEvent& event) const {
- auto* target = static_cast<aura::Window*>(event.target());
- return capture_mode_settings_widget_ &&
- capture_mode_settings_widget_->GetNativeWindow()->Contains(target);
- }
- } // namespace ash
|