Skip to content

Commit

Permalink
feat: introduce css filters for projector correction
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Dec 27, 2024
1 parent b7db70c commit 5adb743
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 9 deletions.
9 changes: 6 additions & 3 deletions packages/client/internals/FormItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defineProps<{
nested?: boolean | number
div?: boolean
description?: string
dot?: boolean
}>()
const emit = defineEmits<{
Expand All @@ -19,17 +20,19 @@ function reset() {

<template>
<component :is="div ? 'div' : 'label'" flex="~ row gap-2 items-center" select-none>
<div w-30 h-10 flex="~ gap-1 items-center">
<div w-30 h-8 flex="~ gap-1 items-center">
<div
v-if="nested" i-ri-corner-down-right-line op40
:style="typeof nested === 'number' ? { marginLeft: `${nested * 0.5 + 0.5}rem` } : { marginLeft: '0.25rem' }"
/>
<div v-if="!description" op75 @dblclick="reset">
<div v-if="!description" op75 relative @dblclick="reset">
{{ title }}
<div v-if="dot" w-1.5 h-1.5 bg-primary rounded absolute top-0 right--2 />
</div>
<Tooltip v-else distance="10">
<div op75 text-right @dblclick="reset">
<div op75 text-right relative @dblclick="reset">
{{ title }}
<div v-if="dot" w-1.5 h-1.5 bg-primary rounded absolute top-0 right--2 />
</div>
<template #popper>
<div text-sm min-w-90 v-html="description" />
Expand Down
68 changes: 68 additions & 0 deletions packages/client/internals/FormSlider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script setup lang="ts">
const props = defineProps<{
max: number
min: number
step: number
unit?: string
default?: number
}>()
const value = defineModel<number>('modelValue', {
type: Number,
})
</script>

<template>
<div relative h-22px w-60 flex-auto @dblclick="props.default !== undefined ? value = props.default : null">
<input
v-model.number="value" type="range" class="slider"
v-bind="props"
absolute bottom-0 left-0 right-0 top-0 z-10 w-full align-top
>
<span
v-if="props.default != null"
border="r main" absolute bottom-0 top-0 h-full w-1px op75
:style="{
left: `${(props.default - min) / (max - min) * 100}%`,
}"
/>
</div>
<div relative h-22px>
<input v-model.number="value" type="number" v-bind="props" border="~ base rounded" m0 w-20 bg-gray:5 pl2 align-top text-sm>
<span v-if="props.unit" pointer-events-none absolute right-1 top-0.5 text-xs op25>{{ props.unit }}</span>
</div>
</template>

<style>
.slider {
appearance: none;
height: 22px;
outline: none;
opacity: 0.7;
-webkit-transition: 0.2s;
transition: opacity 0.2s;
--uno: border border-main rounded of-hidden bg-gray/5;
}
.slider:hover {
opacity: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 5px;
height: 22px;
background: var(--slidev-theme-primary);
cursor: pointer;
z-index: 10;
}
.slider::-moz-range-thumb {
width: 5px;
height: 22px;
background: var(--slidev-theme-primary);
cursor: pointer;
z-index: 10;
}
</style>
3 changes: 2 additions & 1 deletion packages/client/internals/NavControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useDrawings } from '../composables/useDrawings'
import { useNav } from '../composables/useNav'
import { configs } from '../env'
import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
import { activeElement, breakpoints, fullscreen, presenterLayout, showEditor, showInfoDialog, showPresenterCursor, toggleOverview, togglePresenterLayout } from '../state'
import { activeElement, breakpoints, fullscreen, hasViewerCssFilter, presenterLayout, showEditor, showInfoDialog, showPresenterCursor, toggleOverview, togglePresenterLayout } from '../state'
import { downloadPDF } from '../utils'
import IconButton from './IconButton.vue'
import MenuButton from './MenuButton.vue'
Expand Down Expand Up @@ -167,6 +167,7 @@ if (__SLIDEV_FEATURE_RECORD__)
<template #button>
<IconButton title="More Options">
<div class="i-carbon:settings-adjust" />
<div v-if="hasViewerCssFilter" w-2 h-2 bg-primary rounded-full absolute top-0.5 right-0.5 />
</IconButton>
</template>
<template #menu>
Expand Down
76 changes: 75 additions & 1 deletion packages/client/internals/Settings.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { useWakeLock } from '@vueuse/core'
import { useNav } from '../composables/useNav'
import { hideCursorIdle, slideScale, wakeLockEnabled } from '../state'
import { hideCursorIdle, slideScale, viewerCssFilter, viewerCssFilterDefaults, wakeLockEnabled } from '../state'
import FormCheckbox from './FormCheckbox.vue'
import FormItem from './FormItem.vue'
import FormSlider from './FormSlider.vue'
import SegmentControl from './SegmentControl.vue'
const { isPresenter } = useNav()
Expand All @@ -12,6 +13,79 @@ const { isSupported } = useWakeLock()

<template>
<div text-sm select-none flex="~ col gap-1" min-w-30 px4>
<FormItem
title="Invert"
:dot="viewerCssFilter.invert !== viewerCssFilterDefaults.invert"
@reset="viewerCssFilter.invert = viewerCssFilterDefaults.invert"
>
<FormCheckbox v-model="viewerCssFilter.invert" />
</FormItem>
<FormItem
title="Brightness"
:dot="viewerCssFilter.brightness !== viewerCssFilterDefaults.brightness"
@reset="viewerCssFilter.brightness = viewerCssFilterDefaults.brightness"
>
<FormSlider
v-model="viewerCssFilter.brightness"
:max="1.5"
:min="0.5"
:step="0.02"
:default="viewerCssFilterDefaults.brightness"
/>
</FormItem>
<FormItem
title="Contrast"
:dot="viewerCssFilter.contrast !== viewerCssFilterDefaults.contrast"
@reset="viewerCssFilter.contrast = viewerCssFilterDefaults.contrast"
>
<FormSlider
v-model="viewerCssFilter.contrast"
:max="1.5"
:min="0.5"
:step="0.02"
:default="viewerCssFilterDefaults.contrast"
/>
</FormItem>
<FormItem
title="Saturation"
:dot="viewerCssFilter.saturate !== viewerCssFilterDefaults.saturate"
@reset="viewerCssFilter.saturate = viewerCssFilterDefaults.saturate"
>
<FormSlider
v-model="viewerCssFilter.saturate"
:max="1.5"
:min="0.5"
:step="0.02"
:default="viewerCssFilterDefaults.saturate"
/>
</FormItem>
<FormItem
title="Sepia"
:dot="viewerCssFilter.sepia !== viewerCssFilterDefaults.sepia"
@reset="viewerCssFilter.sepia = viewerCssFilterDefaults.sepia"
>
<FormSlider
v-model="viewerCssFilter.sepia"
:max="2"
:min="-2"
:step="0.02"
:default="viewerCssFilterDefaults.sepia"
/>
</FormItem>
<FormItem
title="Hue Rotate"
:dot="viewerCssFilter.hueRotate !== viewerCssFilterDefaults.hueRotate"
@reset="viewerCssFilter.hueRotate = viewerCssFilterDefaults.hueRotate"
>
<FormSlider
v-model="viewerCssFilter.hueRotate"
:max="180"
:min="-180"
:step="0.1"
:default="viewerCssFilterDefaults.hueRotate"
/>
</FormItem>
<div class="h-1px opacity-5 bg-current w-full my2" />
<FormItem
v-if="!isPresenter"
title="Slide Scale"
Expand Down
20 changes: 18 additions & 2 deletions packages/client/internals/SlideContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
contentStyle: {
type: Object,
default: () => ({}),
},
})
const { isPrintMode } = useNav()
Expand All @@ -45,6 +49,7 @@ const scale = computed(() => {
})
const contentStyle = computed(() => ({
...props.contentStyle,
'height': `${slideHeight.value}px`,
'width': `${slideWidth.value}px`,
'transform': `translate(-50%, -50%) scale(${scale.value})`,
Expand Down Expand Up @@ -73,8 +78,19 @@ const snapshot = computed(() => {
</script>

<template>
<div v-if="!snapshot" :id="isMain ? 'slide-container' : undefined" ref="container" class="slidev-slide-container" :style="containerStyle">
<div :id="isMain ? 'slide-content' : undefined" ref="slideElement" class="slidev-slide-content" :style="contentStyle">
<div
v-if="!snapshot"
:id="isMain ? 'slide-container' : undefined"
ref="container"
class="slidev-slide-container"
:style="containerStyle"
>
<div
:id="isMain ? 'slide-content' : undefined"
ref="slideElement"
class="slidev-slide-content"
:style="contentStyle"
>
<slot />
</div>
<slot name="controls" />
Expand Down
22 changes: 21 additions & 1 deletion packages/client/pages/play.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SlideContainer from '../internals/SlideContainer.vue'
import SlidesShow from '../internals/SlidesShow.vue'
import { onContextMenu } from '../logic/contextMenu'
import { registerShortcuts } from '../logic/shortcuts'
import { editorHeight, editorWidth, isEditorVertical, isScreenVertical, showEditor } from '../state'
import { editorHeight, editorWidth, isEditorVertical, isScreenVertical, showEditor, viewerCssFilter, viewerCssFilterDefaults } from '../state'
const { next, prev, isPrintMode, isPresenter } = useNav()
const { isDrawing } = useDrawings()
Expand Down Expand Up @@ -61,6 +61,25 @@ const persistNav = computed(() => isScreenVertical.value || showEditor.value)
const SideEditor = shallowRef<any>()
if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)
const contentStyle = computed(() => {
let filter = ''
if (viewerCssFilter.value.brightness !== viewerCssFilterDefaults.brightness)
filter += `brightness(${viewerCssFilter.value.brightness}) `
if (viewerCssFilter.value.contrast !== viewerCssFilterDefaults.contrast)
filter += `contrast(${viewerCssFilter.value.contrast}) `
if (viewerCssFilter.value.sepia !== viewerCssFilterDefaults.sepia)
filter += `sepia(${viewerCssFilter.value.sepia}) `
if (viewerCssFilter.value.hueRotate !== viewerCssFilterDefaults.hueRotate)
filter += `hue-rotate(${viewerCssFilter.value.hueRotate}deg) `
if (viewerCssFilter.value.invert)
filter += 'invert(1) '
return {
filter,
}
})
</script>

<template>
Expand All @@ -71,6 +90,7 @@ if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
<SlideContainer
:style="{ background: 'var(--slidev-slide-container-background, black)' }"
is-main
:content-style="contentStyle"
@pointerdown="onClick"
@contextmenu="onContextMenu"
>
Expand Down
18 changes: 18 additions & 0 deletions packages/client/state/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ export const activeDragElement = shallowRef<DragElementState | null>(null)
export const presenterNotesFontSize = useLocalStorage('slidev-presenter-font-size', 1, { listenToStorageChanges: false })
export const presenterLayout = useLocalStorage('slidev-presenter-layout', 1, { listenToStorageChanges: false })

export const viewerCssFilterDefaults = {
invert: false,
contrast: 1,
brightness: 1,
hueRotate: 0,
saturate: 1,
sepia: 0,
}
export const viewerCssFilter = useLocalStorage(
'slidev-viewer-css-filter',
viewerCssFilterDefaults,
{ listenToStorageChanges: false, mergeDefaults: true, deep: true },
)
export const hasViewerCssFilter = computed(() => {
return (Object.keys(viewerCssFilterDefaults) as (keyof typeof viewerCssFilterDefaults)[])
.some(k => viewerCssFilter.value[k] !== viewerCssFilterDefaults[k])
})

export function togglePresenterLayout() {
presenterLayout.value = presenterLayout.value + 1
if (presenterLayout.value > 3)
Expand Down
2 changes: 1 addition & 1 deletion packages/client/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ html {
user-select: none;
outline: none;
cursor: pointer;
@apply inline-flex items-center justify-center opacity-75 transition duration-200 ease-in-out align-middle rounded p-1;
@apply inline-flex items-center justify-center opacity-75 transition duration-200 ease-in-out align-middle rounded p-1 relative;
@apply hover:(opacity-100 bg-gray-400 bg-opacity-10);
@apply focus-visible:(opacity-100 outline outline-2 outline-offset-2 outline-black dark:outline-white);
@apply md:p-2;
Expand Down

0 comments on commit 5adb743

Please sign in to comment.