Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add zoom #149

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 59 additions & 4 deletions streamlit_drawable_canvas/frontend/src/DrawableCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
canvas.fireRightClick = true

const [backgroundCanvas, setBackgroundCanvas] = useState(
new fabric.StaticCanvas("")
new fabric.Canvas("")
)
const {
canvasState: {
action: { shouldReloadCanvas, forceSendToStreamlit },
action: { shouldReloadCanvas, forceSendToStreamlit, resetView },
currentState,
initialState,
},
Expand All @@ -87,6 +87,7 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
canRedo,
forceStreamlitUpdate,
resetState,
initalView,
} = useCanvasState()

/**
Expand All @@ -97,7 +98,7 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
const c = new fabric.Canvas("canvas", {
enableRetinaScaling: false,
})
const imgC = new fabric.StaticCanvas("backgroundimage-canvas", {
const imgC = new fabric.Canvas("backgroundimage-canvas", {
enableRetinaScaling: false,
})
setCanvas(c)
Expand Down Expand Up @@ -125,7 +126,7 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
if (backgroundImageURL) {
var bgImage = new Image();
bgImage.onload = function() {
backgroundCanvas.getContext().drawImage(bgImage, 0, 0);
backgroundCanvas.add(new fabric.Image(bgImage));
};
const baseUrl = getStreamlitBaseUrl() ?? ""
bgImage.src = baseUrl + backgroundImageURL
Expand All @@ -149,6 +150,17 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
}
}, [canvas, shouldReloadCanvas, currentState])

/**
* Reset view on resetView state change
*/
useEffect(() => {
if (resetView) {
canvas.setZoom(1.0);
backgroundCanvas.setZoom(1.0);
canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
backgroundCanvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
}
}, [canvas, backgroundCanvas, resetView])
/**
* Update canvas with selected tool
* PS: add initialDrawing in dependency so user drawing update reinits tool
Expand All @@ -174,6 +186,46 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
saveState(canvas.toJSON())
})

function setZoom(canvas: fabric.Canvas, e: WheelEvent) {
var delta = e.deltaY;
var offsetX = e.offsetX;
var offsetY = e.offsetY;
var zoom = canvas.getZoom();

zoom *= 0.999 ** delta;
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
canvas.zoomToPoint(new fabric.Point(offsetX, offsetY), zoom);

var vpt = canvas.viewportTransform? canvas.viewportTransform : [1, 0, 0, 1, 0, 0];
if (zoom < 400 / 1000) {
vpt[4] = 200 - 1000 * zoom / 2;
vpt[5] = 200 - 1000 * zoom / 2;
} else {
if (vpt[4] >= 0) {
vpt[4] = 0;
} else if (vpt[4] < canvas.getWidth() - 1000 * zoom) {
vpt[4] = canvas.getWidth() - 1000 * zoom;
}
if (vpt[5] >= 0) {
vpt[5] = 0;
} else if (vpt[5] < canvas.getHeight() - 1000 * zoom) {
vpt[5] = canvas.getHeight() - 1000 * zoom;
}
}
}

canvas.on('mouse:wheel', function(opt: any) {
setZoom(canvas, opt.e);
setZoom(backgroundCanvas, opt.e);

opt.e.preventDefault();
opt.e.stopPropagation();
});




// Cleanup tool + send data to Streamlit events
return () => {
cleanupToolEvents()
Expand All @@ -182,6 +234,7 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
}
}, [
canvas,
backgroundCanvas,
strokeWidth,
strokeColor,
displayRadius,
Expand All @@ -190,6 +243,7 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
initialDrawing,
saveState,
forceStreamlitUpdate,
initalView,
])

/**
Expand Down Expand Up @@ -256,6 +310,7 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
resetCallback={() => {
resetState(initialState)
}}
resetZoomCallback={initalView}
/>
)}
</div>
Expand Down
25 changes: 24 additions & 1 deletion streamlit_drawable_canvas/frontend/src/DrawableCanvasState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,37 @@ interface CanvasHistory {
interface CanvasAction {
shouldReloadCanvas: boolean // reload currentState into app canvas, on undo/redo
forceSendToStreamlit: boolean // send currentState back to Streamlit
resetView: boolean // reset view to initial state
}

const NO_ACTION: CanvasAction = {
shouldReloadCanvas: false,
forceSendToStreamlit: false,
resetView: false,
}

const RELOAD_CANVAS: CanvasAction = {
shouldReloadCanvas: true,
forceSendToStreamlit: false,
resetView: false,
}

const SEND_TO_STREAMLIT: CanvasAction = {
shouldReloadCanvas: false,
forceSendToStreamlit: true,
resetView: false,
}

const RELOAD_AND_SEND_TO_STREAMLIT: CanvasAction = {
shouldReloadCanvas: true,
forceSendToStreamlit: true,
resetView: false,
}

const RESET_VIEW: CanvasAction = {
shouldReloadCanvas: false,
forceSendToStreamlit: false,
resetView: true,
}

interface CanvasState {
Expand All @@ -46,7 +57,7 @@ interface CanvasState {
}

interface Action {
type: "save" | "undo" | "redo" | "reset" | "forceSendToStreamlit"
type: "save" | "undo" | "redo" | "reset" | "forceSendToStreamlit" | "resetView"
state?: Object
}

Expand Down Expand Up @@ -181,6 +192,14 @@ const canvasStateReducer = (
initialState: state.initialState,
currentState: state.currentState,
}

case "resetView":
return {
history: { ...state.history },
action: { ...RESET_VIEW },
initialState: state.initialState,
currentState: state.currentState,
}
default:
throw new Error("TS should protect from this")
}
Expand All @@ -194,6 +213,7 @@ const initialState: CanvasState = {
action: {
forceSendToStreamlit: false,
shouldReloadCanvas: false,
resetView: false,
},
initialState: {},
currentState: {},
Expand All @@ -208,6 +228,7 @@ interface CanvasStateContextProps {
canUndo: boolean
canRedo: boolean
resetState: (state: Object) => void
initalView: () => void
}

const CanvasStateContext = createContext<CanvasStateContextProps>(
Expand Down Expand Up @@ -235,6 +256,7 @@ export const CanvasStateProvider = ({
(state) => dispatch({ type: "reset", state: state }),
[dispatch]
)
const initalView = useCallback(() => dispatch({ type: "resetView" }), [dispatch])

const canUndo = canvasState.history.undoStack.length !== 0
const canRedo = canvasState.history.redoStack.length !== 0
Expand All @@ -250,6 +272,7 @@ export const CanvasStateProvider = ({
canRedo,
forceStreamlitUpdate,
resetState,
initalView,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import styles from "./CanvasToolbar.module.css"
import bin from "../img/bin.png"
import undo from "../img/undo.png"
import download from "../img/download.png"
import resetZoom from "../img/reset_zoom.png"

interface SquareIconProps {
imgUrl: string
Expand Down Expand Up @@ -50,6 +51,7 @@ interface CanvasToolbarProps {
undoCallback: () => void
redoCallback: () => void
resetCallback: () => void
resetZoomCallback: () => void
}

const CanvasToolbar = ({
Expand All @@ -61,6 +63,7 @@ const CanvasToolbar = ({
undoCallback,
redoCallback,
resetCallback,
resetZoomCallback,
}: CanvasToolbarProps) => {
const GAP_BETWEEN_ICONS = 4
const ICON_SIZE = 24
Expand Down Expand Up @@ -94,6 +97,13 @@ const CanvasToolbar = ({
enabled: true,
clickCallback: resetCallback,
},
{
imgUrl: resetZoom,
altText: "Reset zoom",
invertX: false,
enabled: true,
clickCallback: resetZoomCallback,
},
]

return (
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.