macOS: make conversation and main header draggable
This commit is contained in:
parent
ecc04d36de
commit
729d808f62
7 changed files with 119 additions and 11 deletions
|
@ -21,8 +21,10 @@ body {
|
||||||
// It'd be great if we could use the `:fullscreen` selector here, but that does not seem
|
// It'd be great if we could use the `:fullscreen` selector here, but that does not seem
|
||||||
// to work with Electron, at least on macOS.
|
// to work with Electron, at least on macOS.
|
||||||
--title-bar-drag-area-height: 0px; // Needs to have a unit to work with `calc()`.
|
--title-bar-drag-area-height: 0px; // Needs to have a unit to work with `calc()`.
|
||||||
|
--draggable-app-region: initial;
|
||||||
&.os-macos:not(.full-screen) {
|
&.os-macos:not(.full-screen) {
|
||||||
--title-bar-drag-area-height: 28px;
|
--title-bar-drag-area-height: 28px;
|
||||||
|
--draggable-app-region: drag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4217,6 +4217,8 @@ button.module-conversation-details__action-button {
|
||||||
// Module: Main Header
|
// Module: Main Header
|
||||||
|
|
||||||
.module-main-header {
|
.module-main-header {
|
||||||
|
-webkit-app-region: var(--draggable-app-region);
|
||||||
|
|
||||||
height: calc(#{$header-height} + var(--title-bar-drag-area-height));
|
height: calc(#{$header-height} + var(--title-bar-drag-area-height));
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@ -4236,11 +4238,16 @@ button.module-conversation-details__action-button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
&__search {
|
&__search {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
@ -4388,6 +4395,7 @@ button.module-conversation-details__action-button {
|
||||||
|
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
@include color-svg($icon, $color-gray-90);
|
@include color-svg($icon, $color-gray-90);
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
--button-spacing: 16px;
|
--button-spacing: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-webkit-app-region: var(--draggable-app-region);
|
||||||
padding-top: var(--title-bar-drag-area-height);
|
padding-top: var(--title-bar-drag-area-height);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
height: calc(#{$header-height} + var(--title-bar-drag-area-height));
|
height: calc(#{$header-height} + var(--title-bar-drag-area-height));
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
|
@ -78,6 +78,7 @@
|
||||||
&--clickable {
|
&--clickable {
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
// These are clobbered by button-reset:
|
// These are clobbered by button-reset:
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
@ -171,6 +172,7 @@
|
||||||
&__button {
|
&__button {
|
||||||
$icon-size: 32px;
|
$icon-size: 32px;
|
||||||
|
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
import { DataMessageClass } from './textsecure.d';
|
import { DataMessageClass } from './textsecure.d';
|
||||||
import { MessageAttributesType } from './model-types.d';
|
import { MessageAttributesType } from './model-types.d';
|
||||||
import { WhatIsThis } from './window.d';
|
import { WhatIsThis } from './window.d';
|
||||||
|
import { getTitleBarVisibility, TitleBarVisibility } from './types/Settings';
|
||||||
|
import { isWindowDragElement } from './util/isWindowDragElement';
|
||||||
import { assert } from './util/assert';
|
import { assert } from './util/assert';
|
||||||
|
|
||||||
export async function startApp(): Promise<void> {
|
export async function startApp(): Promise<void> {
|
||||||
|
@ -78,16 +80,14 @@ export async function startApp(): Promise<void> {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('dblclick', (event: Event) => {
|
if (getTitleBarVisibility() === TitleBarVisibility.Hidden) {
|
||||||
const target = event.target as HTMLElement;
|
window.addEventListener('dblclick', (event: Event) => {
|
||||||
const isDoubleClickOnTitleBar = Boolean(
|
const target = event.target as HTMLElement;
|
||||||
target.classList.contains('module-title-bar-drag-area') ||
|
if (isWindowDragElement(target)) {
|
||||||
target.closest('module-title-bar-drag-area')
|
window.titleBarDoubleClick();
|
||||||
);
|
}
|
||||||
if (isDoubleClickOnTitleBar) {
|
});
|
||||||
window.titleBarDoubleClick();
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Globally disable drag and drop
|
// Globally disable drag and drop
|
||||||
document.body.addEventListener(
|
document.body.addEventListener(
|
||||||
|
|
|
@ -367,6 +367,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
{({ ref }) => (
|
{({ ref }) => (
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarPath={avatarPath}
|
avatarPath={avatarPath}
|
||||||
|
className="module-main-header__avatar"
|
||||||
color={color}
|
color={color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
|
74
ts/test-electron/util/isWindowDragElement_test.ts
Normal file
74
ts/test-electron/util/isWindowDragElement_test.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import { isWindowDragElement } from '../../util/isWindowDragElement';
|
||||||
|
|
||||||
|
describe('isWindowDragElement', () => {
|
||||||
|
const crel = (tagName: string, appRegion?: string): Element => {
|
||||||
|
const result = document.createElement(tagName);
|
||||||
|
if (appRegion) {
|
||||||
|
result.style.cssText = `-webkit-app-region: ${appRegion}`;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
let sandboxEl: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandboxEl = document.createElement('div');
|
||||||
|
document.body.appendChild(sandboxEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandboxEl.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for elements with no -webkit-app-region property in the heirarchy', () => {
|
||||||
|
const root = crel('div');
|
||||||
|
const outer = crel('span');
|
||||||
|
const inner = crel('div');
|
||||||
|
root.appendChild(outer);
|
||||||
|
outer.appendChild(inner);
|
||||||
|
sandboxEl.appendChild(root);
|
||||||
|
|
||||||
|
assert.isFalse(isWindowDragElement(root));
|
||||||
|
assert.isFalse(isWindowDragElement(outer));
|
||||||
|
assert.isFalse(isWindowDragElement(inner));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for elements with -webkit-app-region: drag on a sub-element', () => {
|
||||||
|
const parent = crel('div');
|
||||||
|
const child = crel('div', 'drag');
|
||||||
|
parent.appendChild(child);
|
||||||
|
sandboxEl.appendChild(parent);
|
||||||
|
|
||||||
|
assert.isFalse(isWindowDragElement(parent));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if any element up the chain is found to be -webkit-app-region: no-drag', () => {
|
||||||
|
const root = crel('div', 'drag');
|
||||||
|
const outer = crel('div', 'no-drag');
|
||||||
|
const inner = crel('div');
|
||||||
|
root.appendChild(outer);
|
||||||
|
outer.appendChild(inner);
|
||||||
|
sandboxEl.appendChild(root);
|
||||||
|
|
||||||
|
assert.isFalse(isWindowDragElement(outer));
|
||||||
|
assert.isFalse(isWindowDragElement(inner));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if any element up the chain is found to be -webkit-app-region: drag', () => {
|
||||||
|
const root = crel('div', 'drag');
|
||||||
|
const outer = crel('div');
|
||||||
|
const inner = crel('div');
|
||||||
|
root.appendChild(outer);
|
||||||
|
outer.appendChild(inner);
|
||||||
|
sandboxEl.appendChild(root);
|
||||||
|
|
||||||
|
assert.isTrue(isWindowDragElement(root));
|
||||||
|
assert.isTrue(isWindowDragElement(outer));
|
||||||
|
assert.isTrue(isWindowDragElement(inner));
|
||||||
|
});
|
||||||
|
});
|
21
ts/util/isWindowDragElement.ts
Normal file
21
ts/util/isWindowDragElement.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
export function isWindowDragElement(el: Readonly<Element>): boolean {
|
||||||
|
let currentEl: Element | null = el;
|
||||||
|
do {
|
||||||
|
const appRegion = getComputedStyle(currentEl).getPropertyValue(
|
||||||
|
'-webkit-app-region'
|
||||||
|
);
|
||||||
|
switch (appRegion) {
|
||||||
|
case 'no-drag':
|
||||||
|
return false;
|
||||||
|
case 'drag':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
currentEl = currentEl.parentElement;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (currentEl);
|
||||||
|
return false;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue