Hi, so I did some testing and I think the most confusing part is the reversed shifting during zoom out. Since the image section is increased during zoom out normally no additional focus is needed that's why I have disabled shifting during zoom out. This again might result into a little problem with Calc since it doesn't focus the center during zoom but always keeps the upper left cell where it is. But the upper left cell anchor only does make sense for Calc without mouse centered zooming because with it the user can control the focus during zooming in. For example without mouse centered zooming if a user wants to increase the view of a cell on the right or bottom it does get shifted out of the screen while zooming in so s/he would always have to use the scroll bars afterwards. I haven't changed the focus of Calc with this patch yet but if my current concept looks more promising then the last I can implement screen centered zooming for Calc so all LO apps would act the same. So what do you think? Regards Tim On 04/16/2012 01:29 PM, Michael Meeks wrote:
Hi Tim, On Thu, 2012-04-12 at 21:35 +0200, Tim Hardeck wrote:During zoom out the direction is reversed to get back near the starting point. This is disabled for Calc since it doesn't focus the center but instead keeps the upper left cell where it is so reversing the zoom direction feels not intuitive.I gave this a test, and it's unclear quite what to expect here. eg. I create a draw document, insert a stock box shape in the top right, move my mouse to over the top of a specific corner of that, and scroll-in. What I'd expect (perhaps) is that the point that the mouse is over grows large around that point, ie. that point would be the equivalent of the center between two fingers of 'pinch to zoom' ;-) it seems instead that what is (was) underneath the mouse moves to the centre of the screen - is that intended ? Also - if I zoom in, move the mouse and zoom in again - then zoom out the behaviour can seem a little strange ... IMHO we need some more UI thought / testing around this before merging (sorry to be a pain ! ;-) hopefully there are other, easier things to be done in parallel while the spec. firms up - and sorry for landing you with this deeply vague task ;-) Thanks ! Michael.
-- SUSE LINUX Products GmbH, GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer, HRB 16746 (AG Nürnberg) Maxfeldstr. 5, 90409 Nürnberg, Germany T: +49 (0) 911 74053-0 F: +49 (0) 911 74053-483 http://www.suse.de/
From 700f5c6b1695042176f61ebdc6a459ea439a3534 Mon Sep 17 00:00:00 2001 From: Tim Hardeck <thardeck@suse.com> Date: Thu, 22 Mar 2012 16:50:19 +0100 Subject: [PATCH] Mouse centered zooming This patch integrates mouse centered zooming which means that the position of the mouse is used as a target during zooming. So the screen position is shifted in the direction of the pointer. If the mouse is near the center (currently 20% to each side) the focus isn't changed. During zoom out the mouse position is ignored since the image section is increased and not narrowed. Except of Writer only the the mouse wheel scrolling is affected by this patch. The zooming behavior of Writer was changed to focus the screen center instead of the upper left corner. --- basegfx/inc/basegfx/tools/zoomtools.hxx | 8 ++++++- basegfx/source/tools/zoomtools.cxx | 38 ++++++++++++++++++++++++++++++- sc/source/ui/inc/tabview.hxx | 2 +- sc/source/ui/inc/viewdata.hxx | 4 ++-- sc/source/ui/view/tabview.cxx | 9 +++++++- sc/source/ui/view/tabview5.cxx | 4 ++-- sc/source/ui/view/viewdata.cxx | 19 +++++++++++++--- sd/source/ui/inc/ViewShell.hxx | 2 +- sd/source/ui/inc/Window.hxx | 2 +- sd/source/ui/view/sdwindow.cxx | 20 +++++++++++++--- sd/source/ui/view/viewshe2.cxx | 4 ++-- sd/source/ui/view/viewshel.cxx | 2 +- sw/source/ui/inc/view.hxx | 6 +++-- sw/source/ui/uiview/viewmdi.cxx | 31 +++++++++++++++++++++---- sw/source/ui/uiview/viewport.cxx | 6 ++++- 15 files changed, 131 insertions(+), 26 deletions(-) diff --git a/basegfx/inc/basegfx/tools/zoomtools.hxx b/basegfx/inc/basegfx/tools/zoomtools.hxx index 44d9052..ec11043 100644 --- a/basegfx/inc/basegfx/tools/zoomtools.hxx +++ b/basegfx/inc/basegfx/tools/zoomtools.hxx @@ -36,12 +36,18 @@ namespace basegfx { - /** This namespace provides functions for optimized geometric zooming + /** + * This namespace provides functions for optimized geometric and + * mouse centered zooming. */ namespace zoomtools { + // geometric zooming BASEGFX_DLLPUBLIC long zoomOut(long nCurrent); BASEGFX_DLLPUBLIC long zoomIn(long nCurrent); + + // mouse centered zooming + BASEGFX_DLLPUBLIC long shiftZoomPos( long nMousePos, long nWinSize, long nShiftSpeed, bool bZoomIn ); } } diff --git a/basegfx/source/tools/zoomtools.cxx b/basegfx/source/tools/zoomtools.cxx index 34b0b8c..d8154d3 100644 --- a/basegfx/source/tools/zoomtools.cxx +++ b/basegfx/source/tools/zoomtools.cxx @@ -32,7 +32,6 @@ namespace basegfx { namespace zoomtools { - /** 2^(1/6) as the default step This ensures (unless the rounding is used) that 6 steps lead @@ -129,6 +128,43 @@ long zoomOut(long nCurrent) nNew = enforceStep(nNew, nCurrent, 25); return nNew; } + + +/** +* Returns how far and in which direction to move the screen position by +* considering the current mouse location in relation to the center. +* 0 is returned if the difference between the mouse position and the center +* is below nMouseTolerance. +* If bZoomIn is false the mouse position is ignored. +* (return value is reversed to allow getting back near the starting position). +* +* @param nMousePos mouse position on the appropriate axis +* @param nWinSize length of the appropriate window size +* @param nShiftSpeed distance to move in one direction +* @param bZoomIn true if zooming in +*/ +long shiftZoomPos( long nMousePos, long nWinSize, long nShiftSpeed, bool bZoomIn) +{ + // the tolerance value is used on each side of the center so the + // actual tolerance size is twice as big + long nMouseTolerance = nWinSize * 0.2; + short nShift = 0; + + if( (nMousePos - nWinSize / 2) / nMouseTolerance != 0 ) + { + if(nMousePos < nWinSize / 2) + nShift--; + else + nShift++; + + // don't shift during zoom out + if (!bZoomIn) + //nShift *= -1; + nShift = 0; + } + + return( nShift * nShiftSpeed ); +} } // namespace zoomtools } // namespace basegfx diff --git a/sc/source/ui/inc/tabview.hxx b/sc/source/ui/inc/tabview.hxx index 8879b07..bf60b58 100644 --- a/sc/source/ui/inc/tabview.hxx +++ b/sc/source/ui/inc/tabview.hxx @@ -310,7 +310,7 @@ public: * @param bSameTabButMoved true if the same sheet as before is activated. */ void TabChanged( bool bSameTabButMoved = false ); - void SetZoom( const Fraction& rNewX, const Fraction& rNewY, bool bAll ); + void SetZoom( const Fraction& rNewX, const Fraction& rNewY, bool bAll, const Point& rMousePos = Point(), bool bZoomIn = true ); SC_DLLPUBLIC void RefreshZoom(); void SetPagebreakMode( bool bSet ); diff --git a/sc/source/ui/inc/viewdata.hxx b/sc/source/ui/inc/viewdata.hxx index 6ae8da2..32bf425 100644 --- a/sc/source/ui/inc/viewdata.hxx +++ b/sc/source/ui/inc/viewdata.hxx @@ -323,8 +323,8 @@ public: void SetZoomType( SvxZoomType eNew, sal_Bool bAll ); void SetZoomType( SvxZoomType eNew, std::vector< SCTAB >& tabs ); - void SetZoom( const Fraction& rNewX, const Fraction& rNewY, std::vector< SCTAB >& tabs ); - void SetZoom( const Fraction& rNewX, const Fraction& rNewY, sal_Bool bAll ); + void SetZoom( const Fraction& rNewX, const Fraction& rNewY, std::vector< SCTAB >& tabs, const Point& rMousePos = Point(), bool bZoomIn = true ); + void SetZoom( const Fraction& rNewX, const Fraction& rNewY, sal_Bool bAll, const Point& rMousePos = Point(), bool bZoomIn = true ); void RefreshZoom(); void SetSelCtrlMouseClick( bool bTmp ) { bSelCtrlMouseClick = bTmp; } diff --git a/sc/source/ui/view/tabview.cxx b/sc/source/ui/view/tabview.cxx index 219f9d2..266d430 100644 --- a/sc/source/ui/view/tabview.cxx +++ b/sc/source/ui/view/tabview.cxx @@ -1042,8 +1042,15 @@ bool ScTabView::ScrollCommand( const CommandEvent& rCEvt, ScSplitPos ePos ) const Fraction& rOldY = aViewData.GetZoomY(); long nOld = (long)(( rOldY.GetNumerator() * 100 ) / rOldY.GetDenominator()); long nNew = nOld; + bool bZoomIn = true; if ( pData->GetDelta() < 0 ) + { nNew = Max( (long) MINZOOM, basegfx::zoomtools::zoomOut( nOld )); + // commented out because Calc doesn't focus the center but + // instead keeps the upper left cell where it is so reversing + // the zoom direction feels not intuitive + bZoomIn = false; + } else nNew = Min( (long) MAXZOOM, basegfx::zoomtools::zoomIn( nOld )); @@ -1054,7 +1061,7 @@ bool ScTabView::ScrollCommand( const CommandEvent& rCEvt, ScSplitPos ePos ) sal_Bool bSyncZoom = SC_MOD()->GetAppOptions().GetSynchronizeZoom(); SetZoomType( SVX_ZOOM_PERCENT, bSyncZoom ); Fraction aFract( nNew, 100 ); - SetZoom( aFract, aFract, bSyncZoom ); + SetZoom( aFract, aFract, bSyncZoom, rCEvt.GetMousePosPixel(), bZoomIn ); PaintGrid(); PaintTop(); PaintLeft(); diff --git a/sc/source/ui/view/tabview5.cxx b/sc/source/ui/view/tabview5.cxx index 3d30fad..64ac6d8 100644 --- a/sc/source/ui/view/tabview5.cxx +++ b/sc/source/ui/view/tabview5.cxx @@ -387,9 +387,9 @@ void ScTabView::SetZoomType( SvxZoomType eNew, bool bAll ) aViewData.SetZoomType( eNew, bAll ); } -void ScTabView::SetZoom( const Fraction& rNewX, const Fraction& rNewY, bool bAll ) +void ScTabView::SetZoom( const Fraction& rNewX, const Fraction& rNewY, bool bAll, const Point& rMousePos, bool bZoomIn ) { - aViewData.SetZoom( rNewX, rNewY, bAll ); + aViewData.SetZoom( rNewX, rNewY, bAll, rMousePos, bZoomIn ); if (pDrawView) pDrawView->RecalcScale(); ZoomChanged(); // einzeln wegen CLOOKs diff --git a/sc/source/ui/view/viewdata.cxx b/sc/source/ui/view/viewdata.cxx index 73c79a6..6e707bb 100644 --- a/sc/source/ui/view/viewdata.cxx +++ b/sc/source/ui/view/viewdata.cxx @@ -72,6 +72,8 @@ #include <comphelper/string.hxx> #include <com/sun/star/container/XNameContainer.hpp> +#include <basegfx/tools/zoomtools.hxx> + using namespace com::sun::star; #define SC_GROWY_SMALL_EXTRA 100 @@ -601,8 +603,19 @@ void ScViewData::SetZoomType( SvxZoomType eNew, sal_Bool bAll ) SetZoomType( eNew, vTabs ); } -void ScViewData::SetZoom( const Fraction& rNewX, const Fraction& rNewY, std::vector< SCTAB >& tabs ) +void ScViewData::SetZoom( const Fraction& rNewX, const Fraction& rNewY, std::vector< SCTAB >& tabs, const Point& rMousePos, bool bZoomIn ) { + // use mouse centered zooming if a mouse position is passed on + if( rMousePos != Point() ) + { + long nX1 = Max( 0L, basegfx::zoomtools::shiftZoomPos( rMousePos.X(), aScrSize.Width(), 1, bZoomIn ) + GetPosX(SC_SPLIT_LEFT) ); + long nY1 = Max( 0L, basegfx::zoomtools::shiftZoomPos( rMousePos.Y(), aScrSize.Height(), 1, bZoomIn ) + GetPosY(SC_SPLIT_BOTTOM) ); + + SetActivePart( SC_SPLIT_BOTTOMLEFT ); + SetPosX( SC_SPLIT_LEFT, nX1 ); + SetPosY( SC_SPLIT_BOTTOM, nY1 ); + } + sal_Bool bAll = ( tabs.empty() ); if ( !bAll ) // create associated table data CreateTabData( tabs ); @@ -675,7 +688,7 @@ void ScViewData::SetZoom( const Fraction& rNewX, const Fraction& rNewY, std::vec RefreshZoom(); } -void ScViewData::SetZoom( const Fraction& rNewX, const Fraction& rNewY, sal_Bool bAll ) +void ScViewData::SetZoom( const Fraction& rNewX, const Fraction& rNewY, sal_Bool bAll, const Point& rMousePos, bool bZoomIn ) { std::vector< SCTAB > vTabs; if ( !bAll ) // get selected tabs @@ -683,7 +696,7 @@ void ScViewData::SetZoom( const Fraction& rNewX, const Fraction& rNewY, sal_Bool ScMarkData::iterator itr = mpMarkData->begin(), itrEnd = mpMarkData->end(); vTabs.insert(vTabs.begin(), itr, itrEnd); } - SetZoom( rNewX, rNewY, vTabs ); + SetZoom( rNewX, rNewY, vTabs, rMousePos, bZoomIn ); } void ScViewData::SetShowGrid( bool bShow ) diff --git a/sd/source/ui/inc/ViewShell.hxx b/sd/source/ui/inc/ViewShell.hxx index 58fc6af..7f39e8f 100644 --- a/sd/source/ui/inc/ViewShell.hxx +++ b/sd/source/ui/inc/ViewShell.hxx @@ -224,7 +224,7 @@ public: virtual void UpdateScrollBars (void); void Scroll(long nX, long nY); void ScrollLines(long nX, long nY); - virtual void SetZoom(long nZoom); + void SetZoom(long nZoom, const Point& rMousePos = Point(), bool bZoomIn = true); virtual void SetZoomRect(const Rectangle& rZoomRect); void InitWindows(const Point& rViewOrigin, const Size& rViewSize, const Point& rWinPos, sal_Bool bUpdate = sal_False); diff --git a/sd/source/ui/inc/Window.hxx b/sd/source/ui/inc/Window.hxx index 1842903..d340e2d 100644 --- a/sd/source/ui/inc/Window.hxx +++ b/sd/source/ui/inc/Window.hxx @@ -72,7 +72,7 @@ public: @param nZoom The zoom factor is given as integral percent value. */ - void SetZoomIntegral(long nZoom); + void SetZoomIntegral(long nZoom, const Point& rMousePos = Point(), bool bZoomIn = true); /** This internally used method performs the actual adaption of the window's map mode to the specified zoom factor. diff --git a/sd/source/ui/view/sdwindow.cxx b/sd/source/ui/view/sdwindow.cxx index 0590702..25c0c9e 100644 --- a/sd/source/ui/view/sdwindow.cxx +++ b/sd/source/ui/view/sdwindow.cxx @@ -48,6 +48,8 @@ #include "AccessibleDrawDocumentView.hxx" #include "WindowUpdater.hxx" +#include <basegfx/tools/zoomtools.hxx> + namespace sd { #define SCROLL_LINE_FACT 0.05 // Faktor fuer Zeilenscrolling @@ -487,7 +489,7 @@ long Window::SetZoomFactor(long nZoom) return nZoom; } -void Window::SetZoomIntegral(long nZoom) +void Window::SetZoomIntegral(long nZoom, const Point& rMousePos, bool bZoomIn) { // Clip the zoom factor to the valid range marked by nMinZoom as // previously calculated by <member>CalcMinZoom()</member> and the @@ -501,8 +503,20 @@ void Window::SetZoomIntegral(long nZoom) Size aSize = PixelToLogic(GetOutputSizePixel()); long nW = aSize.Width() * GetZoom() / nZoom; long nH = aSize.Height() * GetZoom() / nZoom; - maWinPos.X() += (aSize.Width() - nW) / 2; - maWinPos.Y() += (aSize.Height() - nH) / 2; + + // shift zoom direction + long nShiftX = 0; + long nShiftY = 0; + + // use mouse centered zooming if a mouse position is passed on + if( rMousePos != Point() ) + { + nShiftX = basegfx::zoomtools::shiftZoomPos( rMousePos.X(), GetOutputSizePixel().Width(), nW * 0.08, bZoomIn ); + nShiftY = basegfx::zoomtools::shiftZoomPos( rMousePos.Y(), GetOutputSizePixel().Height(), nH * 0.08, bZoomIn ); + } + + maWinPos.X() += (aSize.Width() - nW) / 2 + nShiftX; + maWinPos.Y() += (aSize.Height() - nH) / 2 + nShiftY; if ( maWinPos.X() < 0 ) maWinPos.X() = 0; if ( maWinPos.Y() < 0 ) maWinPos.Y() = 0; diff --git a/sd/source/ui/view/viewshe2.cxx b/sd/source/ui/view/viewshe2.cxx index afd7ac0..ee5f386 100644 --- a/sd/source/ui/view/viewshe2.cxx +++ b/sd/source/ui/view/viewshe2.cxx @@ -374,7 +374,7 @@ void ViewShell::Scroll(long nScrollX, long nScrollY) |* \************************************************************************/ -void ViewShell::SetZoom(long nZoom) +void ViewShell::SetZoom(long nZoom, const Point& rMousePos, bool bZoomIn) { Fraction aUIScale(nZoom, 100); aUIScale *= GetDoc()->GetUIScale(); @@ -387,7 +387,7 @@ void ViewShell::SetZoom(long nZoom) if (mpContentWindow.get() != NULL) { - mpContentWindow->SetZoomIntegral(nZoom); + mpContentWindow->SetZoomIntegral(nZoom, rMousePos, bZoomIn); // #i74769# Here is a 2nd way (besides Window::Scroll) to set the visible prt // of the window. It needs - like Scroll(SCROLL_CHILDREN) does - also to move diff --git a/sd/source/ui/view/viewshel.cxx b/sd/source/ui/view/viewshel.cxx index fdbead9..4c33926 100644 --- a/sd/source/ui/view/viewshel.cxx +++ b/sd/source/ui/view/viewshel.cxx @@ -719,7 +719,7 @@ bool ViewShell::HandleScrollCommand(const CommandEvent& rCEvt, ::sd::Window* pWi else nNewZoom = Min( (long) pWin->GetMaxZoom(), basegfx::zoomtools::zoomIn( nOldZoom )); - SetZoom( nNewZoom ); + SetZoom( nNewZoom, rCEvt.GetMousePosPixel(), nOldZoom < nNewZoom ); Invalidate( SID_ATTR_ZOOM ); Invalidate( SID_ATTR_ZOOMSLIDER ); diff --git a/sw/source/ui/inc/view.hxx b/sw/source/ui/inc/view.hxx index 229e8f5..5670887 100644 --- a/sw/source/ui/inc/view.hxx +++ b/sw/source/ui/inc/view.hxx @@ -365,7 +365,9 @@ class SW_DLLPUBLIC SwView: public SfxViewShell SW_DLLPRIVATE void _SetZoom( const Size &rEditSz, SvxZoomType eZoomType, short nFactor = 100, - sal_Bool bViewOnly = sal_False); + sal_Bool bViewOnly = sal_False, + const Point& rMousePos = Point(), + bool bZoomIn = true ); SW_DLLPRIVATE void CalcAndSetBorderPixel( SvBorder &rToFill, sal_Bool bInner ); SW_DLLPRIVATE void ShowAtResize(); @@ -506,7 +508,7 @@ public: // insert frames void InsFrmMode(sal_uInt16 nCols); - void SetZoom( SvxZoomType eZoomType, short nFactor = 100, sal_Bool bViewOnly = sal_False); + void SetZoom( SvxZoomType eZoomType, short nFactor = 100, sal_Bool bViewOnly = sal_False, const Point& rMousePos = Point(), bool bZoomIn = true ); virtual void SetZoomFactor( const Fraction &rX, const Fraction & ); void SetViewLayout( sal_uInt16 nColumns, bool bBookMode, sal_Bool bViewOnly = sal_False ); diff --git a/sw/source/ui/uiview/viewmdi.cxx b/sw/source/ui/uiview/viewmdi.cxx index 27949cf..dac7c87 100644 --- a/sw/source/ui/uiview/viewmdi.cxx +++ b/sw/source/ui/uiview/viewmdi.cxx @@ -62,6 +62,8 @@ #include <IDocumentSettingAccess.hxx> #include <PostItMgr.hxx> +#include <basegfx/tools/zoomtools.hxx> + sal_uInt16 SwView::nMoveType = NID_PGE; sal_Int32 SwView::nActMark = 0; @@ -71,13 +73,12 @@ sal_Int32 SwView::nActMark = 0; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::frame; -void SwView::SetZoom( SvxZoomType eZoomType, short nFactor, sal_Bool bViewOnly ) +void SwView::SetZoom( SvxZoomType eZoomType, short nFactor, sal_Bool bViewOnly, const Point& rMousePos, bool bZoomIn ) { - _SetZoom( GetEditWin().GetOutputSizePixel(), eZoomType, nFactor, bViewOnly ); + _SetZoom( GetEditWin().GetOutputSizePixel(), eZoomType, nFactor, bViewOnly, rMousePos, bZoomIn ); } -void SwView::_SetZoom( const Size &rEditSize, SvxZoomType eZoomType, - short nFactor, sal_Bool bViewOnly ) +void SwView::_SetZoom( const Size &rEditSize, SvxZoomType eZoomType, short nFactor, sal_Bool bViewOnly, const Point& rMousePos, bool bZoomIn ) { sal_Bool bUnLockView = !pWrtShell->IsViewLocked(); pWrtShell->LockView( sal_True ); @@ -167,6 +168,28 @@ void SwView::_SetZoom( const Size &rEditSize, SvxZoomType eZoomType, } if ( pOpt->GetZoom() != (sal_uInt16) nFac ) { + Size aSize = GetEditWin().PixelToLogic(rEditSize); + long nW = aSize.Width() * pOpt->GetZoom() / nFac; + long nH = aSize.Height() * pOpt->GetZoom() / nFac; + + // shift zoom direction + long nShiftX = 0; + long nShiftY = 0; + + // use mouse centered zooming if a mouse position is passed on + if( rMousePos != Point() ) + { + nShiftX = basegfx::zoomtools::shiftZoomPos( rMousePos.X(), rEditSize.Width(), nW * 0.25, bZoomIn ); + nShiftY = basegfx::zoomtools::shiftZoomPos( rMousePos.Y(), rEditSize.Height(), nH * 0.25, bZoomIn ); + } + + Point aPos = aVisArea.TopLeft(); + aPos.X() += (aSize.Width() - nW) / 2 + nShiftX; + aPos.Y() += (aSize.Height() - nH) / 2 + nShiftY; + aPos.X() = Max( 0L, aPos.X() ); + aPos.Y() = Max( 0L, aPos.Y() ); + SetVisArea( aPos ); + aOpt.SetZoom ( sal_uInt16(nFac) ); aOpt.SetReadonly(pOpt->IsReadonly()); pWrtShell->ApplyViewOptions( aOpt ); diff --git a/sw/source/ui/uiview/viewport.cxx b/sw/source/ui/uiview/viewport.cxx index 44bd8c4..5315c41 100644 --- a/sw/source/ui/uiview/viewport.cxx +++ b/sw/source/ui/uiview/viewport.cxx @@ -1289,13 +1289,17 @@ sal_Bool SwView::HandleWheelCommands( const CommandEvent& rCEvt ) const CommandWheelData* pWData = rCEvt.GetWheelData(); if( pWData && COMMAND_WHEEL_ZOOM == pWData->GetMode() ) { + bool bZoomIn = true; long nFact = pWrtShell->GetViewOptions()->GetZoom(); if( 0L > pWData->GetDelta() ) + { nFact = Max( (long) 20, basegfx::zoomtools::zoomOut( nFact )); + bZoomIn = false; + } else nFact = Min( (long) 600, basegfx::zoomtools::zoomIn( nFact )); - SetZoom( SVX_ZOOM_PERCENT, nFact ); + SetZoom( SVX_ZOOM_PERCENT, nFact, sal_False, rCEvt.GetMousePosPixel(), bZoomIn ); bOk = sal_True; } else -- 1.7.9.2
Attachment:
signature.asc
Description: OpenPGP digital signature