diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index e2454336b8..b24f548d60 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -16,11 +16,14 @@ limitations under the License. .mx_AppearanceUserSettingsTab_fontSlider, .mx_AppearanceUserSettingsTab_fontSlider_preview, -.mx_AppearanceUserSettingsTab_Layout, -.mx_AppearanceUserSettingsTab .mx_Field { +.mx_AppearanceUserSettingsTab_Layout { @mixin mx_Settings_fullWidthField; } +.mx_AppearanceUserSettingsTab .mx_Field { + width: 256px; +} + .mx_AppearanceUserSettingsTab_fontScaling { color: $primary-fg-color; } @@ -204,3 +207,14 @@ limitations under the License. background-color: rgba($accent-color, 0.08); } } + +.mx_AppearanceUserSettingsTab_Advanced { + .mx_AppearanceUserSettingsTab_AdvancedToggle { + color: $accent-color; + margin-bottom: 16px; + } + + .mx_AppearanceUserSettingsTab_systemFont { + margin-left: calc($font-16px + 10px); + } +} diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index fbee431d6e..834edff7df 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -54,6 +54,8 @@ interface IProps { // If specified, contents will appear as a tooltip on the element and // validation feedback tooltips will be suppressed. tooltipContent?: React.ReactNode; + // If specified the tooltip will be shown regardless of feedback + forceTooltipVisible?: boolean; // If specified alongside tooltipContent, the class name to apply to the // tooltip itself. tooltipClassName?: string; @@ -242,10 +244,9 @@ export default class Field extends React.PureComponent<PropShapes, IState> { const Tooltip = sdk.getComponent("elements.Tooltip"); let fieldTooltip; if (tooltipContent || this.state.feedback) { - const addlClassName = tooltipClassName ? tooltipClassName : ''; fieldTooltip = <Tooltip - tooltipClassName={`mx_Field_tooltip ${addlClassName}`} - visible={this.state.feedbackVisible} + tooltipClassName={classNames("mx_Field_tooltip", tooltipClassName)} + visible={(this.state.focused && this.props.forceTooltipVisible) || this.state.feedbackVisible} label={tooltipContent || this.state.feedback} />; } diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 46723ec7cd..e935663bbe 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -21,7 +21,6 @@ import {_t} from "../../../../../languageHandler"; import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore"; import { enumerateThemes } from "../../../../../theme"; import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher"; -import Field from "../../../elements/Field"; import Slider from "../../../elements/Slider"; import AccessibleButton from "../../../elements/AccessibleButton"; import dis from "../../../../../dispatcher/dispatcher"; @@ -32,6 +31,7 @@ import { IValidationResult, IFieldState } from '../../../elements/Validation'; import StyledRadioButton from '../../../elements/StyledRadioButton'; import StyledCheckbox from '../../../elements/StyledCheckbox'; import SettingsFlag from '../../../elements/SettingsFlag'; +import Field from '../../../elements/Field'; import EventTilePreview from '../../../elements/EventTilePreview'; interface IProps { @@ -55,6 +55,9 @@ interface IState extends IThemeState { customThemeUrl: string; customThemeMessage: CustomThemeMessage; useCustomFontSize: boolean; + useSystemFont: boolean; + systemFont: string; + showAdvanced: boolean; useIRCLayout: boolean; } @@ -73,6 +76,9 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I customThemeUrl: "", customThemeMessage: {isError: false, text: ""}, useCustomFontSize: SettingsStore.getValue("useCustomFontSize"), + useSystemFont: SettingsStore.getValue("useSystemFont"), + systemFont: SettingsStore.getValue("systemFont"), + showAdvanced: false, useIRCLayout: SettingsStore.getValue("useIRCLayout"), }; } @@ -374,6 +380,47 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I </div>; }; + private renderAdvancedSection() { + const toggle = <div + className="mx_AppearanceUserSettingsTab_AdvancedToggle" + onClick={() => this.setState({showAdvanced: !this.state.showAdvanced})} + > + {this.state.showAdvanced ? "Hide advanced" : "Show advanced"} + </div>; + + let advanced: React.ReactNode; + + if (this.state.showAdvanced) { + advanced = <div> + <SettingsFlag + name="useSystemFont" + level={SettingLevel.DEVICE} + useCheckbox={true} + onChange={(checked) => this.setState({useSystemFont: checked})} + /> + <Field + className="mx_AppearanceUserSettingsTab_systemFont" + label={SettingsStore.getDisplayName("systemFont")} + onChange={(value) => { + this.setState({ + systemFont: value.target.value, + }); + + SettingsStore.setValue("systemFont", null, SettingLevel.DEVICE, value.target.value); + }} + tooltipContent="Set the name of a font installed on your system & Riot will attempt to use it." + forceTooltipVisible={true} + disabled={!this.state.useSystemFont} + value={this.state.systemFont} + /> + </div>; + } + return <div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_Advanced"> + {toggle} + {advanced} + </div>; + } + render() { return ( <div className="mx_SettingsTab mx_AppearanceUserSettingsTab"> @@ -384,6 +431,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I {this.renderThemeSection()} {SettingsStore.isFeatureEnabled("feature_font_scaling") ? this.renderFontSection() : null} {SettingsStore.isFeatureEnabled("feature_irc_ui") ? this.renderLayoutSection() : null} + {this.renderAdvancedSection()} </div> ); } diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 5f7ca1293c..379a0a4451 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -69,4 +69,14 @@ export enum Action { * Opens the user menu (previously known as the top left menu). No additional payload information required. */ ToggleUserMenu = "toggle_user_menu", + + /** + * Sets the apps root font size. Should be used with UpdateFontSizePayload + */ + UpdateFontSize = "update_font_size", + + /** + * Sets a system font. Should be used with UpdateSystemFontPayload + */ + UpdateSystemFont = "update_system_font", } diff --git a/src/dispatcher/payloads/UpdateFontSizePayload.ts b/src/dispatcher/payloads/UpdateFontSizePayload.ts new file mode 100644 index 0000000000..6577acd594 --- /dev/null +++ b/src/dispatcher/payloads/UpdateFontSizePayload.ts @@ -0,0 +1,27 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface UpdateFontSizePayload extends ActionPayload { + action: Action.UpdateFontSize; + + /** + * The font size to set the root to + */ + size: number; +} diff --git a/src/dispatcher/payloads/UpdateSystemFontPayload.ts b/src/dispatcher/payloads/UpdateSystemFontPayload.ts new file mode 100644 index 0000000000..aa59db5aa9 --- /dev/null +++ b/src/dispatcher/payloads/UpdateSystemFontPayload.ts @@ -0,0 +1,32 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface UpdateSystemFontPayload extends ActionPayload { + action: Action.UpdateSystemFont; + + /** + * Specify whether to use a system font or the stylesheet font + */ + useSystemFont: boolean; + + /** + * The system font to use + */ + font: string; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 646f43af33..9982578506 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -460,6 +460,8 @@ "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", "Match system theme": "Match system theme", + "Use a system font": "Use a system font", + "System font name": "System font name", "Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls", "Send analytics data": "Send analytics data", "Never send encrypted messages to unverified sessions from this session": "Never send encrypted messages to unverified sessions from this session", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 028f355ab8..eb882b2d18 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -30,6 +30,8 @@ import PushToMatrixClientController from './controllers/PushToMatrixClientContro import ReloadOnChangeController from "./controllers/ReloadOnChangeController"; import {RIGHT_PANEL_PHASES} from "../stores/RightPanelStorePhases"; import FontSizeController from './controllers/FontSizeController'; +import SystemFontController from './controllers/SystemFontController'; +import UseSystemFontController from './controllers/UseSystemFontController'; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = ['device', 'room-device', 'room-account', 'account', 'config']; @@ -319,6 +321,18 @@ export const SETTINGS = { default: true, displayName: _td("Match system theme"), }, + "useSystemFont": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: false, + displayName: _td("Use a system font"), + controller: new UseSystemFontController(), + }, + "systemFont": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: "", + displayName: _td("System font name"), + controller: new SystemFontController(), + }, "webRtcAllowPeerToPeer": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, displayName: _td('Allow Peer-to-Peer for 1:1 calls'), diff --git a/src/settings/controllers/FontSizeController.js b/src/settings/controllers/FontSizeController.ts similarity index 80% rename from src/settings/controllers/FontSizeController.js rename to src/settings/controllers/FontSizeController.ts index 3ef01ab99b..6440fd32fe 100644 --- a/src/settings/controllers/FontSizeController.js +++ b/src/settings/controllers/FontSizeController.ts @@ -16,6 +16,8 @@ limitations under the License. import SettingController from "./SettingController"; import dis from "../../dispatcher/dispatcher"; +import { UpdateFontSizePayload } from "../../dispatcher/payloads/UpdateFontSizePayload"; +import { Action } from "../../dispatcher/actions"; export default class FontSizeController extends SettingController { constructor() { @@ -24,8 +26,8 @@ export default class FontSizeController extends SettingController { onChange(level, roomId, newValue) { // Dispatch font size change so that everything open responds to the change. - dis.dispatch({ - action: "update-font-size", + dis.dispatch<UpdateFontSizePayload>({ + action: Action.UpdateFontSize, size: newValue, }); } diff --git a/src/settings/controllers/SystemFontController.ts b/src/settings/controllers/SystemFontController.ts new file mode 100644 index 0000000000..4f591efc17 --- /dev/null +++ b/src/settings/controllers/SystemFontController.ts @@ -0,0 +1,36 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SettingController from "./SettingController"; +import SettingsStore from "../SettingsStore"; +import dis from "../../dispatcher/dispatcher"; +import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload"; +import { Action } from "../../dispatcher/actions"; + +export default class SystemFontController extends SettingController { + constructor() { + super(); + } + + onChange(level, roomId, newValue) { + // Dispatch font size change so that everything open responds to the change. + dis.dispatch<UpdateSystemFontPayload>({ + action: Action.UpdateSystemFont, + useSystemFont: SettingsStore.getValue("useSystemFont"), + font: newValue, + }); + } +} diff --git a/src/settings/controllers/UseSystemFontController.ts b/src/settings/controllers/UseSystemFontController.ts new file mode 100644 index 0000000000..d598b25962 --- /dev/null +++ b/src/settings/controllers/UseSystemFontController.ts @@ -0,0 +1,36 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SettingController from "./SettingController"; +import SettingsStore from "../SettingsStore"; +import dis from "../../dispatcher/dispatcher"; +import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload"; +import { Action } from "../../dispatcher/actions"; + +export default class UseSystemFontController extends SettingController { + constructor() { + super(); + } + + onChange(level, roomId, newValue) { + // Dispatch font size change so that everything open responds to the change. + dis.dispatch<UpdateSystemFontPayload>({ + action: Action.UpdateSystemFont, + useSystemFont: newValue, + font: SettingsStore.getValue("systemFont"), + }); + } +} diff --git a/src/settings/watchers/FontWatcher.ts b/src/settings/watchers/FontWatcher.ts index 5527284cd0..9af5156704 100644 --- a/src/settings/watchers/FontWatcher.ts +++ b/src/settings/watchers/FontWatcher.ts @@ -18,6 +18,8 @@ import dis from '../../dispatcher/dispatcher'; import SettingsStore, {SettingLevel} from '../SettingsStore'; import IWatcher from "./Watcher"; import { toPx } from '../../utils/units'; +import { Action } from '../../dispatcher/actions'; +import { UpdateSystemFontPayload } from '../../dispatcher/payloads/UpdateSystemFontPayload'; export class FontWatcher implements IWatcher { public static readonly MIN_SIZE = 8; @@ -33,6 +35,10 @@ export class FontWatcher implements IWatcher { public start() { this.setRootFontSize(SettingsStore.getValue("baseFontSize")); + this.setSystemFont({ + useSystemFont: SettingsStore.getValue("useSystemFont"), + font: SettingsStore.getValue("systemFont"), + }); this.dispatcherRef = dis.register(this.onAction); } @@ -41,8 +47,10 @@ export class FontWatcher implements IWatcher { } private onAction = (payload) => { - if (payload.action === 'update-font-size') { + if (payload.action === Action.UpdateFontSize) { this.setRootFontSize(payload.size); + } else if (payload.action === Action.UpdateSystemFont) { + this.setSystemFont(payload); } }; @@ -54,4 +62,8 @@ export class FontWatcher implements IWatcher { } (<HTMLElement>document.querySelector(":root")).style.fontSize = toPx(fontSize); }; + + private setSystemFont = ({useSystemFont, font}) => { + document.body.style.fontFamily = useSystemFont ? font : ""; + }; }